前端自動化測試解決方案探析
- 2019 年 12 月 4 日
- 筆記
本文作者:IMWeb ouven 原文出處:IMWeb社區 未經同意,禁止轉載
前端測試一直是前端項目開發過程中機器重要的一個環節,高效的測試方法可以減少我們進行代碼自測的時間,提高我們的開發效率,如果你的代碼涉及的測試用例較多,而且項目需要長期維護,這時就可以考慮使用一下自動化測試了。
一、前端自動化測試
前端自動化測試一般是指是在預設條件下運行前端頁面或邏輯模塊,評估運行結果。預設條件應包括正常條件和異常條件,以達到自動運行測試過程、減少或避免人工干預測試的目的。在前端自動化測試中,我們通常是通過不同的工具來解決不同場景下不同的問題的。就測試類型來看,主要分為BDD(Bebavior Driven Developement,行為驅動測試)和TDD(Testing Driven Developement,測試驅動開發)。BDD可以讓項目成員(甚至是不懂編程的)使用自然描述語言來描述系統功能和業務邏輯,從而根據這些描述步驟進行系統自動化的測試;TDD則要求在編寫某個功能的代碼之前先編寫測試代碼,然後只編寫使測試通過的功能代碼,通過測試來推動整個開發的進行。這有助於編寫簡潔可用和高質量的代碼,並加速實際開發過程
BDD和TDD均有各自的適用場景,BDD一般更偏向於系統功能和業務邏輯的自動化測試設計,而TDD在快速開發並測試功能模塊的過程中則更加高效,以快速完成開發為目的。下面我們看下BDD和TDD具體的特點:
BDD的特點:
- 從業務邏輯的角度定義具體的輸入與預期輸出,以及可衡量的目標;
- 儘可能覆蓋所有的測試用例情況;
- 描述一系列可執行的行為,根據業務的分析來定義預期輸出。例如,expect, should, assert;
- 設定關鍵的測試通過節點輸出提示,便於測試人員理解;
- 最大程度的交付出符合用戶期望的產品,避免輸出不一致帶來的問題。
TDD的特點:
- 需求分析,快速編寫對應的輸入輸出測試腳本;
- 實現代碼讓測試為成功;
- 重構,然後重複測試,最終讓程序符合所有要求。
二、單元測試解決方案
就前端而言,單元測試的實現工具比較多。主要有mocha,jasmine和qunit。我們先來看看使用mocha是怎樣實現單元測試的。
- mocha
mocha的特點是簡單可擴展、支持瀏覽器和Node、支持同步和異步、支持連續用例測試。測試集,以函數describe(string, function)封裝;測試用例,以it(string, function)函數封裝,它包含2個參數;斷言,以assert語句表示,返回true或false。另外,mocha在完成異步測試用例時通過done()來標記。
$ npm install mocha $ mkdir test $ $EDITOR test/test.js # or open with your favorite editor
測試用例:
var assert = require('assert'); describe('Array', function() { describe('#indexOf()', function() { it('should return -1 when the value is not present', function() { assert.equal(-1, [1,2,3].indexOf(4)); }); }); });
輸出為:
$ ./node_modules/mocha/bin/mocha Array #indexOf() ✓ should return -1 when the value is not present 1 passing (9ms)
同時,mocha支持異步和Promise。
describe('#find()', function() { it('respond with matching records', function(done) { db.find({type: 'User'}, function(err, res) { if (err) return done(err); res.should.have.length(3); done(); }); }); }); });
- jasmine。
jasmine是一個BTT的框架,不依賴其它框架。測試集以函數describe(string, function)封裝;測試用例,以it(string, function)函數封裝,它也包含2個參數;斷言,以expect語句表示,返回true或false;斷言的比較操作時,將Expectation傳入的實際值和Matcher傳入的期望值比較,另外任何Matcher都能通過在expect調用Matcher前加上not來實現一個否定的斷言(expect(a).not().toBe(false);)
describe("A suite is just a function", function() { var a; it("and so is a spec", function() { a = true; expect(a).toBe(true); expect(a).not().toBe(false); }); });
jasmine也支持異步測試用例。
describe("long asynchronous specs", function() { beforeEach(function(done) { done(); }, 1000); it("takes a long time", function(done) { setTimeout(function() { done(); }, 9000); }, 10000); afterEach(function(done) { done(); }, 1000); });
https://jasmine.github.io/2.5/introduction
- qunit
qunit是一個可基於jquery的簡單測試框架,主要運行在瀏覽器端。它通過QUnit.test定義一個測試集,一個測試集中通過回調函數裏面多個斷言判斷來實現多個測試用例,使用起來非常簡單。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>QUnit Example</title> <link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.0.1.css"> </head> <body> <div id="qunit"></div> <div id="qunit-fixture"></div> <script src="https://code.jquery.com/qunit/qunit-2.0.1.js"></script> <script> QUnit.test( "hello test", function( assert ) { assert.ok( 1 == "1", "Passed!" ); assert.equal( null, false, "null, false; equal fails" ); }); </script> </body> </html>
qunit也支持異步測試用例,異步完成時通過done()來結束。
QUnit.test( "assert.async() test", function( assert ) { var done = assert.async(); var input = $( "#test-input" ).focus(); setTimeout(function() { assert.equal( document.activeElement, input[0], "Input was focused" ); done(); }); });
小結一下,單元測試工具的主要組成部分其實是類似的,主要包括測試集、測試用例、斷言和斷言比較等。它可以用來快速測試單元模塊的主要功能,有助於輔助我們快速開發。
三、集成化測試解決方案
除了模塊單元的測試驅動開發,在系統功能測試階段,我們希望自動化完成業務功能正確性的檢測,此時我們就要考慮集成測試方案了。目前前端集成化測試自動化工具也有比較多。例如CasperJS、Nighmare、Nightwatch、Dalekjs,我們來逐個看下。
- casperJS。
casperJS基於PhantomJS或SlimerJS(PhantomJS或SlimerJS都是用於web測試的自動化無界面瀏覽器),可以模擬完成頁面內系統級的自動化操作行為測試。
var casper = require('casper').create(); casper.start('http://casperjs.org/'); casper.then(function() { this.echo('First Page: ' + this.getTitle()); }); casper.thenOpen('http://phantomjs.org', function() { this.echo('Second Page: ' + this.getTitle()); }); casper.run();
輸出內容為:
$ casperjs sample.js First Page: CasperJS - a navigation scripting & testing utility for PhantomJS and SlimerJS written in Javascript Second Page: PhantomJS | PhantomJS
頁面內的操作結合casper的操作就可以這樣來實現。
var casper = require('casper').create(); var links; function getLinks() { // Scrape the links from top-right nav of the website var links = document.querySelectorAll('ul.navigation li a'); return Array.prototype.map.call(links, function (e) { return e.getAttribute('href') }); } // Opens casperjs homepage casper.start('http://casperjs.org/'); casper.then(function () { links = this.evaluate(getLinks); }); casper.run(function () { for(var i in links) { console.log(links[i]); } casper.done(); });
- Nightmare。
類似的,nightmare也是一個模擬還原瀏覽器上業務操作的強大工具,而且更易於使用。同時可以使用chrome的插件daydreem自動錄製生成用戶行為操作的事件序列,更加方便我們進行實際的測試。
yield Nightmare() .goto('http://yahoo.com') .type('input[title="Search"]', 'github nightmare') .click('.searchsubmit');
Nightmare也支持異步操作,並支持多種斷言庫,這裡結合chai.js就可以這樣來使用。
var Nightmare = require('nightmare'); var expect = require('chai').expect; // jshint ignore:line describe('test yahoo search results', function() { it('should find the nightmare github link first', function(done) { var nightmare = Nightmare() nightmare .goto('http://yahoo.com') .type('form[action*="/search"] [name=p]', 'github nightmare') .click('form[action*="/search"] [type=submit]') .wait('#main') .evaluate(function () { return document.querySelector('#main .searchCenterMiddle li a').href }) .end() .then(function(link) { expect(link).to.equal('https://github.com/segmentio/nightmare'); done(); }) }); });
- Nightwatch。
Nightwatch則可以使用node書寫端對端的測試用例,並在Selenium server服務端運行測試,同樣支持同步和異步。
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(); };
- Dalekjs
DalekJS是一個跨瀏覽器平台的前端集成測試框架,可以自動配置啟動本地的瀏覽器,也可以模擬填寫提交表單、點擊、截屏、運行單元測試等豐富的操作。
module.exports = { 'Amazon does its thing': function (test) { test .open('http://www.amazon.com/') .type('#twotabsearchtextbox', 'Blues Brothers VHS') .click('.nav-submit-input') .waitForElement('#result_0') .assert.text('#result_0 .newaps a span').is('The Blues Brothers') .done(); } };
test.open('http://adomain.com') .click('#aquestion') .answer('Rose') .assert.text('#aquestion').is('Rose', 'Awesome she was!') .done();
小結一下,和單元測試相同的是,集成測試和單元測試類似,一般也會對測試預期輸出進行斷言和判斷,不同的是,集成測試的輸入設計和功能流程中涉及到瀏覽器本身的行為模擬,用以代替測試人員手動操作的過程,從而能夠提高測試效率。
四、總結與注意事項
通過對單元測試工具和集成測試工具的概述介紹,我們基本了解了單元測試和集成測試的核心部分和特點,儘管目前主流的測試工具各不相同,但是基本的流程原理確實相同的,小結裏面也為大家做了分析。
當然,還有一些仍需要我們注意的問題。自動化測試不可避免地要求我們去編寫測試用例,會花去一定的事件,我們在實際的項目開發過程中,決定要不要使用自動化的測試方案應該根據具體的場景來決定,如果業務規模並不複雜,而且系統功能流程清晰,則不建議使用測試用例,因為這樣得不償失;但如果業務達到一定規模,需要在原有較大項目繼續維護開發的情況下,編寫測試用例有利於我們較快暴露和定位問題,並極有助於後期的維護。
參考資料:
http://joshldavis.com/2013/05/27/difference-between-tdd-and-bdd/
https://pythonhosted.org/behave/philosophy.html
http://wiki.c2.com/?TestDrivenDevelopment