1. 程式人生 > >腳手架vue-cli系列五:基於Nightwatch的端到端測試環境

腳手架vue-cli系列五:基於Nightwatch的端到端測試環境

不同公司和組織之間的測試效率迥異。在這個富互動和響應式處理隨處可見的時代,很多組織都使用敏捷的方式來開發應用,因此測試自動化也成為軟體專案的必備部分。測試自動化意味著使用軟體工具來反覆執行專案中的測試,併為迴歸測試提供反饋。

端到端測試又簡稱E2E(End-To-End test)測試,它不同於單元測試側重於檢驗函式的輸出結果,端到端測試將盡可能從使用者的視角,對真實系統的訪問行為進行模擬。對於Web應用來說,這意味著需要開啟瀏覽器、載入頁面、執行JavaScript,以及進行與DOM互動等操作。簡言之,單元測試的功能只能確保單個元件的質量,無法測試具體的業務流程是否運作正常,而E2E卻正好與之相反,它是一個更高層次的面對元件與元件之間、使用者與真實環境之間的一種整合性測試 。

E2E測試的意義在於可以通過程式固化和模擬使用者操作,對於開發人員而言,基於E2E測試能極大地提高Web的開發效能,節約開發時間。

先來看看如果沒有E2E測試下的一次從開發到手工測試成功的過程:

這個過程還屬於簡化過的,還沒有包括在觀察結果時要開啟瀏覽器的除錯視窗觀看某些內部的執行變數或者網頁程式碼結構。整個過程都是純人工操作,人工操作最大的問題是一個程式可能要除錯好幾次,同樣的操作就要重複數遍。即使有嚴格的規定,程式設計師們大多都還是隨便地做“通過”式操作,尤其在輸入樣本資料時,絕大多數的程式設計師幾乎都是亂輸,出現得最多的就是各種隨意的數字或者是“aaa”、“asd”、“aws”這樣毫無意義的字元。以這種方式開發出來的程式在驗收時產品經理或者客戶會經常說一句話:“我上次試過是沒有問題的!”這樣的失誤歸根結底不在程式設計師本身,因為這是一種人性!一個人如果重複多次自己都覺得毫無意義的動作時,要不就逃避不做,如果不能逃避就會消極對待。

所以我們應該用更高效、更能彌補人性化缺陷和更有意義的辦法來處理,這就是E2E測試,先來看看如果使用E2E測試後的開發過程將會變成什麼:

從執行測試開始,所有的一切都是自動的!這就是最大的區別,還有更重要的一點是,當我們要寫出E2E測試時就需要對操作需求有深刻的理解,在這一過程中還有很大的機會對使用者的操作進行優化,從而提高使用者體驗。

 Nightwatch

vue-cli的webpack模板也為我們準備了一個當下很流行的E2E測試框架——Nightwatch。

Nightwatch是一套新近問世的基於Node.js的驗收測試框架,使用Selenium WebDriver API以將Web應用測試自動化。它提供了簡單的語法,支援使用JavaScript和CSS選擇器來編寫執行在Selenium伺服器上的端到端測試。

這個框架在配置好後的具體工作流程如下圖所示。

Nightwatch採用Fluent interface模式(https://en.wikipedia.org/wiki/Fluent_interface)來簡化端到端測試的編寫,語法非常簡潔易懂,正如以下程式碼所示。

    this.demoTestGoogle = function (browser) {
      browser
        .url('http://www.google.com')
        

        .waitForElementVisible('body', 1000)
        .setValue('input[type=text]', 'nightwatch')
        .waitForElementVisible('button[name=btnG]', 1000)
        .click('button[name=btnG]')
        .pause(1000)
        .assert.containsText('#main', 'The Night Watch')
        .end();
    }

我們可以從Nightwatch網站找到當前提供特性的列表:

● 簡單但強大的語法。只需要使用JavaScript和CSS選擇器,開發者就能夠非常迅捷地撰寫測試。開發者也不必初始化其他物件和類,只需要編寫測試規範即可。

● 內建命令列測試執行器,允許開發者同時執行全部測試——分組或單個執行。

● 自動管理Selenium伺服器;如果Selenium執行在另一臺機器上,那麼也可以禁用此特性。

● 支援持續整合:內建JUnit XML報表,因此開發者可以在構建過程中,將自己的測試與系統(例如Hudson或Teamcity等)整合。

● 使用CSS選擇器或Xpath,定位並驗證頁面中的元素或是執行命令。

● 易於擴充套件,便於開發者根據需要,實現與自己應用相關的命令。

配置 Nightwatch

要了解Nightwatch的配置和用法,先從工程結構入手。

工程結構
    .
    └── test
          └── e2e
                ├── custom-assertions     // 自定義斷言
                │    └── elementCount.js
                ├── page-objects          // 頁面物件資料夾
                ├── reports               // 輸出報表資料夾
                ├── screenshots           // 自動截圖
                ├── nightwatch.conf.js    // nightwatch 執行配置
                ├── runner.js             // 執行器
                └── specs                 // 測試檔案
                      └── test.spec.js

以上是vue-cli為我們自動建立的Nightwatch工程結構,specs是測試檔案存放的資料夾,nightwatch.conf.js是Nightwatch的執行配置檔案。其他的目錄將會在具體的章節逐一地進行講述。

基本配置

Nightwatch的配置項都集中在nightwatch.conf.js中,其實這個配置也可以是一個JSON格式,採用JSON格式只需要簡單地對配置項寫入一些常量即可。但使用模組的方式進行配置可以執行一些額外的配置程式碼,這樣則顯得更為靈活。以下是我調整過的nightwatch.conf.js檔案內容:

    require('babel-register');
    var config = require('../../config');
    var seleniumServer = require('selenium-server');
    var phantomjs = require('phantomjs-prebuilt');

    module.exports = {
      "src_folders": ["test/e2e/specs"],
      "output_folder": "test/e2e/reports",
      "custom_assertions_path": ["test/e2e/custom-assertions"],
      "page_objects_path": "test/e2e/page-objects",
      "selenium": {
        "start_process": true,
        "server_path": seleniumServer.path,
        "port": 4444,
        "cli_args": {
          "webdriver.chrome.driver": require('chromedriver').path
        }
      },
      "test_settings": {
        "default": {
          "selenium_port": 4444,
          "selenium_host": "localhost",
          "silent": true,
          launch_url:"http://localhost:" + (process.env.PORT || config.dev.port),
          "globals": {
          }
        },
        "chrome": {
          "desiredCapabilities": {
            "browserName": "chrome",
            "javascriptEnabled": true,
            "acceptSslCerts": true
          }
        },
        "firefox": {
          "desiredCapabilities": {
            "browserName": "firefox",
            "javascriptEnabled": true,
            "acceptSslCerts": true
          }
        }
      }
    }                

Nightwatch的配置分為以下三類:

● 基本配置;

● Selenium配置;

● 測試環境配置。

在配置模組中的所有根元素配置項都屬於基本配置,用於控制Nightwatch的全域性性執行的需要。下表為Nightwatch的基本配置項的詳細說明。

Selenium 配置

Selenium是一組軟體工具集,每一個工具都有不同的方法來支援測試自動化。大多數使用Selenium的QA工程師只關注一兩個最能滿足他們專案需求的工具。然而,學習所有的工具你將有更多選擇來解決不同型別的測試自動化問題。這一整套工具具備豐富的測試功能,很好地契合了測試各種型別的網站應用的需要。這些操作非常靈活,有多種選擇來定位UI元素,同時將預期的測試結果和實際的行為進行比較。Selenium一個最關鍵的特性是支援在多瀏覽器平臺上進行測試。

Selenium誕生於2004年,當在ThoughtWorks工作的Jason Huggins在測試一個內部應 用時,作為一個聰明的傢伙,他意識到相對於每次改動都需要手工進行測試,他的時間應該用得更有價值。他開發了一個可以驅動頁面進行互動的JavaScript庫,能讓多瀏覽器自動返回測試結果。那個庫最終變成了Selenium的核心,它是Selenium RC(遠端控制)和Selenium IDE所有功能的基礎。Selenium RC是開拓性的,因為沒有其他產品能讓你使用自己喜歡的語言來控制瀏覽器。

Selenium是一個龐大的工具,所以它也有自己的缺點。由於它使用了基於JavaScript的自動化引擎,而瀏覽器對JavaScript又有很多安全限制,有些事情就難以實現。更糟糕的是,網站應用正變得越來越強大,它們使用了新瀏覽器提供的各種特性,都使得這些限制讓人痛苦不堪。在2006年,一名Google的工程師Simon Stewart開始基於這個專案進行開發,這個專案被命名為WebDriver。此時,Google早已是Selenium的重度使用者,但是測試工程師們不得不繞過它的限制。Simon需要一款能通過瀏覽器和作業系統的本地方法直接和瀏覽器進行通話的測試工具,來解決JavaScript環境沙箱的問題。WebDriver專案的目標就是要解決Selenium的痛點。

Selenium 1 (又叫Selenium RC或Remote Control)在很長一段時間內,Selenium RC都是最主要的Selenium專案,直到WebDriver和Selenium合併而產生了最新且最強大的Selenium 2。Seleinum 1仍然被活躍地支援著(更多是維護),並且提供一些Selenium 2短時間內可能不會支援的特性,包括對多種語言的支援(Java、JavaScript、Ruby、PHP、Python、Perl和C#)和對大多數瀏覽器的支援。

Selenium 2 (又叫Selenium WebDriver)代表了這個專案未來的方向,也是最新被新增到Selenium工具集中的。這個全新的自動化工具提供了很多了不起的特性,包括更內聚和麵向物件的API,並且解決了舊版本限制。Selenium和WebDriver的作者都贊同兩者各具優勢,而兩者的合併使得這個自動化工具更加強健。Selenium 2.0正是於此的產品。它支援WebDriver API及其底層技術,同時也在WebDriver API底下通過Selenium 1技術為移植測試程式碼提供極大的靈活性。此外,為了向後相容,Selenium 2仍然使用Selenium 1的Selenium RC介面。

你可以到http://selenium-release.storage.googleapis.com/index.html下載Selenium的各個穩定版本。

在Vue專案中如果使用vue-cli,那麼Nightwatch將不需要進行任何的附加配置,否則你需要在命令列內安裝Selenium的包裝類庫:

 $ npm i selenium-server -D

Nightwatch能引導Selenium的啟動,實際上我們並沒有必要去修改Selenium伺服器的預設執行配置,在nightwatch.conf.js配置檔案中只需要宣告Selenium伺服器的二進位制執行 檔案的具體路徑即可,這個可以從selenium-server包提供的Selenium包裝物件的path屬性中獲取,而無須將本機的物理路徑寫死到配置檔案內。

    var seleniumServer = require('selenium-server');

    module.exports= {
      "selenium": {        
        "start_process": true,
        "server_path": seleniumServer.path,
        "port": 4444,
        "cli_args": {
          "webdriver.chrome.driver": require('chromedriver').path
        }
      },
      // ... 省略
    }

以下是Selenium的詳細配置項說明:

 cli_args 的配置

● webdriver.firefox.profile:Selenium預設為每個會話建立一個獨立的Firefox配置方案。如果你希望使用新的驅動配置可以在此進行宣告。

● webdriver.chrome.driver:Nightwatch同樣可以使用Chrome瀏覽器載入測試,當然你要先下載一個ChromeDriver的二進位制執行庫對此進行支援。此配置項用於指明ChromeDriver的安裝位置。除此之外,還需要在test_settings配置內使用desiredCapabilities物件為Chrome建立配置方案。

● webdriver.ie.driver:Nightwatch也支援IE,其作用與用法與Chrome相同,此處則不過多贅述。

測試環境配置

test_settings內的專案將應用於所有的測試例項,在E2E測試中我們可以通過Nightwatch提供的預設例項物件browser獲取這些配置值,vue-cli為我們建立了default、firefox和chrome三個環境配置項,default配置是應用於所有環境的基礎配置選項,其他的配置項會自動覆蓋與default相同的配置值。

firefox和chrome這兩個配置項是對兩種瀏覽器的驅動進行描述和配置。對於其他語言或框架而言它們也是常客,但由於效能太低,在實戰中通常只是個擺設,下文中我將會介紹一種實戰效率更高的無頭瀏覽器PhantomJS,對其取而代之。

不要被vue-cli建立預設配置所迷惑,test_settings並不單單只是對瀏覽器的一些基本執行引數的配置,它正確的用法是對E2E測試環境的配置。單元測試只能運行於開發環境內,而E2E卻可以運行於本地環境與網路環境,更準確地說是開發環境與生產環境。所以這個配置項可以用以下的方式進行設定:

    "test_settings": {
        "default": {
          "selenium_port": 4444,
          "selenium_host": "localhost",
          "silent": true,
          launch_url:"http://localhost:" + (process.env.PORT || config.dev.port),
          "globals": {}
        },
        "dev": {
          "desiredCapabilities": {
            "browserName": "chrome",
            "javascriptEnabled": true,
            "acceptSslCerts": true
          }
        },
        "production": {
          "launch_url":"http://www.your-domain.com"
          "desiredCapabilities": {
            "browserName": "firefox",
            "javascriptEnabled": true,
            "acceptSslCerts": true
          }
        }
      }

雖然與原有的配置只是在用詞上做了一點點改變,但用詞的改變將會徹底地改變我們對其的認知與思路!

下表是測試環境配置項的詳細說明:

 執行 E2E 測試

vue-cli已經在package.json中配置了執行測試的指令:

 $ npm run e2e

這個指令是預設啟用Chrome執行環境的,如果指定執行環境可使用--env選項:

 $ npm run e2e --env

使用無頭瀏覽器 PhantomJS

vue-cli webpack腳手架模板非常好用,它將環境的複雜性降低了很多,但是卻沒有很好地詮釋它裡面採用的每個模組的理由和功能,以及它們的使用特點。這對於入門者來說確實是將門檻降到最低點,但從工程化開發的角度來說,只知道有這些環境或者工具的存在是遠遠不夠的,在Nightwatch中就埋了一個這樣的坑。

我們的開發環境在配置Mocha和Karma時就已經安裝了PhantomJS,但如果你細讀Nightwatch的預設配置會驚奇地發現根本沒有采用PhantomJS,只是配置了Chrome和Firefox!問題何在?一個字:慢!

Chrome的啟動是很慢的,我們做E2E這種自動化測試如果用真實瀏覽器的話只能將效能拖下來,生命不能耗費在毫無意義的等待中!所以我們才會選擇PhantomJS!沒有預設配置PhantomJS作為主瀏覽器是這個環境的最大敗筆。

辦法總比問題多,所以如果沒有,我們還可以自己動手來配置,其實方法也很簡單。開啟nightwatch.conf.js,在test_settings配置段的下方加入以下的內容:

    "test_settings": {
      "default": {
        // ...
      }
    },

    "phantom":{
      "desiredCapabilities": {
        "browserName": "phantomjs",
        "javascriptEnabled": true,
        "acceptSslCerts": true,
        "phantomjs.page.settings.userAgent" : "Mozilla/5.0 (Macintosh; Intel MacOS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.80 Safari/537.36",
        "phantomjs.binary.path":"node_modules/phantomjs-prebuilt/bin/phantomjs"
      }
    }

Nightwatch是通過Selenium載入一個GhostDriver來引導PhantomJS瀏覽器的,上面的內容就相當於告訴Selenium載入一個GhostDriver,可執行程式則指向npm上安裝的phantomjs-prebuilt包,再通過這個包來引導安裝在本機上的PhantomJS啟動。

按上文這樣來引用PhantomJS的二進位制程式的地址非常難看,還有原生配置中的Selenium執行程式地址也是一樣的,這裡介紹一個更DRY的方法來處理這些路徑:

    var seleniumServer = require('selenium-server');
    var phantomjs = require('phantomjs-prebuilt');


    module.exports = {
      // ...省略

      "selenium": {
        // ... 省略

        "server_path": seleniumServer.path,
      },

      "test_settings": {
        // ... 省略

        "phantom": {
          "desiredCapabilities": {
            // ... 省略

            "phantomjs.binary.path": phantomjs.path
          }
        }

        // ... 省略
      }
   }

 做完這個簡單的優化後就可以開啟runner.js檔案找到:

    if (opts.indexOf('--env') === -1) {
      opts = opts.concat(['--env', 'chrome'])
    }

 將chrome改為phantom就行了:

    if (opts.indexOf('--env') === -1) {
      opts = opts.concat(['--env', 'phantom'])
    }

重新載入測試程式,在同一臺iMac上的執行速度直接降到了5秒,測試執行速度提升了3倍!如果你有配置更好的機器,將硬碟換成SSD之後會有更驚人的速度。

Nightwatch 與 Cucumber

如果你正在開發的專案的業務複雜性不大,可以直接使用Nightwatch推薦的鏈式呼叫寫法。但是當這種做法真正應用在業務流程較多,或者業務操作相對複雜的應用場景時,你會覺得總有寫不完的E2E測試,因為這麼做E2E測試是沒有辦法一次性覆蓋所有需求的!

E2E測試其實是行為式驅動開發的實現手法,如果跳過了行為式驅動開發的分析部分直接編寫E2E,其結果只能是寫出一堆嚴重碎片化的測試場景,甚至會出現很多根本不應該出現的操作。

幸好Nightwatch具有很好的擴充套件性與相容性,能整合最正統的BDD測試框架Cucumber(https://cucumber.io/)。Cucumber是原生於Ruby世界的BDD框架,但它也有很多的語言實現版本,我們可以安裝一套專門為Nightwatch編寫的Cucumber版本——nightwatch-cucumber(https://github.com/mucsi96/nightwatch-cucumber)。本章只介紹關於環境與工具的配置,而關於如何來應用BDD,內容已經超出了本書的知識範圍,如果有興趣的話可以參考《攀登架構之巔》一書中行為式驅動開發的章節內容。

 $ npm i nightwatch-cucumber -D

然後在~/test/e2e/nightwatch.conf.js檔案中加入對Cucumber的配置:

  // ... 省略
    require('babel-register');
    require('nightwatch-cucumber')({
      
    nightwatchClientAsParameter: true,
      featureFiles: ['test/e2e/features'],
      stepDefinitions: ['test/e2e/features/step_definitions'],      
      jsonReport: 'test/e2e/reports/cucumber.json',
      htmlReport: 'test/e2e/reports/cucumber.html',
      openReport: false
    });