摘要:翻譯自在這篇文章中,我將詳述如何給我們上周開發(fā)的做單元測(cè)試的過程。單元測(cè)試是一種測(cè)試你的項(xiàng)目中每個(gè)最小單元代碼的藝術(shù),是使你的程序思路清晰的基礎(chǔ)。
第一次翻譯技術(shù)文章,肯定很多語(yǔ)句很生疏,有看官的話就見諒,沒有的話也沒人看的到這句話。。
翻譯自:Unit Testing an AngularJS Directive
在這篇文章中,我將詳述如何給我們上周開發(fā)的stepper directive做單元測(cè)試的過程。下周會(huì)講到如何使用Github和Bower進(jìn)行組件分離。
單元測(cè)試是一種測(cè)試你的項(xiàng)目中每個(gè)最小單元代碼的藝術(shù),是使你的程序思路清晰的基礎(chǔ)。一旦所有的測(cè)試通過,這些零散的單元組合在一起也會(huì)運(yùn)行的很好,因?yàn)檫@些單元的行為已經(jīng)被獨(dú)立的驗(yàn)證過了。
單元測(cè)試能夠避免你的代碼出現(xiàn)回歸性BUG,提高代碼的質(zhì)量和可維護(hù)性,使你的代碼在代碼庫(kù)中是可信賴的,從而提高團(tuán)隊(duì)合作的質(zhì)量,使重構(gòu)變得簡(jiǎn)單和快樂: )
單元測(cè)試的另一個(gè)用處是當(dāng)你發(fā)現(xiàn)了一個(gè)新的BUG,你可以為這個(gè)BUG寫一個(gè)單元測(cè)試,當(dāng)你修改了你的代碼,使這個(gè)測(cè)試可以PASS了的時(shí)候,就說(shuō)明這個(gè)BUG已經(jīng)被修復(fù)了。
AngularJS最好的小伙伴兒KarmaJS test runner(一個(gè)能夠在瀏覽器中運(yùn)行測(cè)試同時(shí)生成結(jié)果日志的Node.js server)還有 Jasmine(定義了你的測(cè)試和斷言的語(yǔ)法的庫(kù))。我們使用Grunt-karma將karma集成在我們經(jīng)典且繁重的grunt 工作流中,然后在瀏覽器中運(yùn)行測(cè)試。這里值得注意的是,karma可以將測(cè)試運(yùn)行在遠(yuǎn)程的云瀏覽器中,比如SauceLabs和BrowserStack。
AngularJS是將是經(jīng)過了嚴(yán)密地測(cè)試的,所以趕緊給自己點(diǎn)個(gè)贊,現(xiàn)在就開始寫測(cè)試吧!
術(shù)語(yǔ):
在我們進(jìn)行下一步之前有一些術(shù)語(yǔ)需要說(shuō)明:
spec: 你想要測(cè)試的代碼的說(shuō)明,包括一個(gè)或多個(gè)測(cè)試條件。spec應(yīng)該覆蓋所有預(yù)期行為。
test suite: 一組測(cè)試的集合,定義在Jasmine提供的describe語(yǔ)句塊中,語(yǔ)句塊是可以嵌套的。
test: 測(cè)試說(shuō)明,寫在Jasmin提供的it語(yǔ)句塊中,以一個(gè)或者多個(gè)期望值結(jié)束(譯者按:也就是說(shuō),一個(gè)it語(yǔ)句塊中,一定要有一個(gè)以上的期望值)。
actual: 在你的期望中要被測(cè)試的值。
expected value: 針對(duì)測(cè)試出的真實(shí)值做比較的期望值。(原文:this is the value you test the actual value against.)
matcher: 一個(gè)返回值為Boolean類型的函數(shù),用于比較真實(shí)值跟期望值。結(jié)果返回給jasmine,比如toEqual,toBeGreatherThan,toHaveBeenCalledWith... 你也可以定義你自己的matcher。
expectation: 使用expect函數(shù)測(cè)試一個(gè)值,得到它的返回值,expectation是與一個(gè)得到期望值的matcher函數(shù)鏈接的。(原文:Use the expect function to test a value, called the actual. It is chained with a matcher function, which takes the expected value.)
mock: 一種「stubbed」(不會(huì)翻譯)服務(wù),你可以制造一些假數(shù)據(jù)或方法來(lái)替代程序真正運(yùn)行時(shí)所產(chǎn)生的數(shù)據(jù)。
這有一個(gè)spec文件的例子:
測(cè)試環(huán)境搭建
// a test suite (group of tests) //一組測(cè)試 describe("sample component test", function() { // a single test //多帶帶的測(cè)試 it("ensure addition is correct", function() { // sample expectation // 簡(jiǎn)單的期望 expect(1+1).toEqual(2); // `--- the expected value (2) 期望值是2 // `--- the matcher method (equality) toEqual方法就是matcher函數(shù) // `-- the actual value (2) 真實(shí)值是2 }); // another test // 另一個(gè)測(cè)試 it("ensure substraction is correct", function() { expect(1-1).toEqual(0); }); });
將grunt-karma添加到你項(xiàng)目的依賴中
npm install grunt-karma --save -dev
創(chuàng)建一個(gè)karma-unit.js文件
這里是一個(gè)karma-unit文件的例子
這個(gè)文件定義了如下內(nèi)容:
* 將要被加載到瀏覽器進(jìn)行測(cè)試的JS文件。通常情況下,不僅項(xiàng)目用的庫(kù)和項(xiàng)目本身的文件需要包含在內(nèi),你所要測(cè)試的文件和mock文件也要在這里加載。
* 你想將測(cè)試運(yùn)行在哪款瀏覽器中。
* 怎樣接收到測(cè)試結(jié)果,是命令行里還是在瀏覽器中...?
* 可選插件。
以下是files這一項(xiàng)的例子:
files: [ "http://code.angularjs.org/1.2.1/angular.js", <-- angular sourc "http://code.angularjs.org/1.2.1/angular-mocks.js", <-- angular mocks & test utils "src/angular-stepper.js", <-- our component source code "src/angular-stepper.spec.js" <-- our component test suite ]
注:這里可以添加jquery在里面,如果你需要它幫助你編寫測(cè)試代碼(更強(qiáng)大的選擇器,CSS測(cè)試,尺寸計(jì)算…)
將karma grunt tasks添加到Gruntfile.js中
karma: { unit: { configFile: "karma-unit.js", // run karma in the background background: true, // which browsers to run the tests on browsers: ["Chrome", "Firefox"] } }
然后創(chuàng)建 angular-stepper.spec.js文件,將上面寫的簡(jiǎn)單的測(cè)試代碼粘貼進(jìn)來(lái)。這時(shí)你就可以輕松運(yùn)行grunt karma任務(wù)去觀察你的測(cè)試在瀏覽器中運(yùn)行并且在命令行中生成測(cè)試報(bào)告。
.... Chrome 33.0.1712 (Mac OS X 10.9.0): Executed 2 of 2 SUCCESS (1.65 secs / 0.004 secs) Firefox 25.0.0 (Mac OS X 10.9): Executed 2 of 2 SUCCESS (2.085 secs / 0.006 secs) TOTAL: 4 SUCCESS
上面有四個(gè)點(diǎn),每個(gè)點(diǎn)都代表一個(gè)成功的測(cè)試,這時(shí)你可以看到,兩個(gè)測(cè)試分別運(yùn)行在我們配置的兩個(gè)瀏覽器中了。
哦也~
那么接下來(lái),讓我們寫一些真正的測(cè)試代碼吧: )
給directive編寫單元測(cè)試為我們的組件所編寫的一組單元測(cè)試,又叫做spec的東西,不僅應(yīng)該覆蓋我們所要測(cè)試的組件的所有預(yù)期行為,還要將邊緣情況覆蓋到(比如不合法的輸入、服務(wù)器的異常狀況)。
下面展示的angular-stepper組件的測(cè)試集的精華部分,完整版點(diǎn)這里。我們對(duì)這樣一個(gè)組件的測(cè)試非常簡(jiǎn)單,不需要假數(shù)據(jù)。唯一比較有技巧性的是,我們將我們的directive包含在了一個(gè)form表單下,這樣能夠在使用ngModelController和更新表單驗(yàn)證正確性的情況下正確的運(yùn)行測(cè)試。(注:此處的內(nèi)容需要讀angular-stepper那個(gè)組件的文件才能懂為何要將directive包含在form表單中,如果不想深入了解,可以忽略這句。原文:The only tricky thing is that we wrap our directive inside a form to be able to test that it plays well with ngModelController and updates form validity correctly.)
"; //原文最后一個(gè)標(biāo)簽是感覺是筆誤。 // inject allows you to use AngularJS dependency injection // to retrieve and use other services inject(function($compile) { var form = $compile(tpl)(scope); elm = form.find("div"); }); // $digest is necessary to finalize the directive generation //$digest 方法對(duì)于生成指令是必要的。 scope.$digest(); } describe("initialisation", function() { // before each test in this block, generates a fresh directive beforeEach(function() { compileDirective(); }); // a single test example, check the produced DOM it("should produce 2 buttons and a div", function() { expect(elm.find("button").length).toEqual(2); expect(elm.find("div").length).toEqual(1); }); it("should check validity on init", function() { expect(scope.form.$valid).toBeTruthy(); }); }); it("should update form validity initialy", function() { // test with a min attribute that is out of bounds // first set the min value scope.testMin = 45; // then produce our directive using it compileDirective(""); // this should impact the form validity expect(scope.form.$valid).toBeFalsy(); }); it("decrease button should be disabled when min reached", function() { // test the initial button status compileDirective(""); expect(elm.find("button").attr("disabled")).not.toBeDefined(); // update the scope model value scope.testModel = 40; // force model change propagation scope.$digest(); // validate it has updated the button status expect(elm.find("button").attr("disabled")).toEqual("disabled"); }); // and many others... });
// the describe keyword is used to define a test suite (group of tests) describe("rnStepper directive", function() { // we declare some global vars to be used in the tests var elm, // our directive jqLite element scope; // the scope where our directive is inserted // load the modules we want to test 在跑測(cè)試之前將你要測(cè)試的模塊引入進(jìn)來(lái) beforeEach(module("revolunet.stepper")); // before each test, creates a new fresh scope // the inject function interest is to make use of the angularJS // dependency injection to get some other services in our test inject方法的作用是利用angularJS的依賴注入將我們所需要的服務(wù)注入進(jìn)去 // here we need $rootScope to create a new scope 需要用$rootScope新建一個(gè)scope beforeEach(inject(function($rootScope, $compile) { scope = $rootScope.$new(); scope.testModel = 42; })); function compileDirective(tpl) { // function to compile a fresh directive with the given template, or a default one // compile the tpl with the $rootScope created above // wrap our directive inside a form to be able to test // that our form integration works well (via ngModelController) // our directive instance is then put in the global "elm" variable for further tests if (!tpl) tpl = ""; tpl = "
一些需要注意的點(diǎn):
在要被測(cè)試的scope中,一個(gè)directive需要被compiled(譯者注:也就是上面代碼中的$compile(tpl)(scope);這句話在做的事情)。
一個(gè)非隔離scope可以通過element.scope()方法訪問到。
一個(gè)隔離的scope可以通過element.isolateScope()方法訪問到。
在一個(gè)真正的angular應(yīng)用中,$digest方法是angular通過各種事件(click,inputs,requests...)的反應(yīng)自動(dòng)調(diào)用的。自動(dòng)化測(cè)試不是以真實(shí)的用戶事件為基礎(chǔ)的,所以我們需要手動(dòng)的調(diào)用$digest方法($digest方法負(fù)責(zé)更新所有數(shù)據(jù)綁定)。
額外福利 #1: 實(shí)時(shí)測(cè)試多虧了grunt,當(dāng)我們的文件改動(dòng)的時(shí)候,可以自動(dòng)的進(jìn)行測(cè)試。
如果你想在你的代碼有任何改動(dòng)的時(shí)候都進(jìn)行一次測(cè)試,只要將一段代碼加入到grunt的watch任務(wù)中就行。
js: { files: ["src/*.js"], tasks: ["karma:unit:run", "build"] },
你也可以將grunt的默認(rèn)任務(wù)設(shè)置成這樣:
grunt.registerTask("default", ["karma:unit", "connect", "watch"]);
設(shè)置完后,運(yùn)行g(shù)runt,就可以實(shí)時(shí)的在內(nèi)置的server中跑測(cè)試了。
額外福利 #2:添加測(cè)試覆蓋率報(bào)告作為開發(fā)者,我們希望以靠譜的數(shù)據(jù)作為依據(jù),我們也希望持續(xù)的改進(jìn)自己的代碼。"coverage"指的是你的測(cè)試代碼的覆蓋率,它可以提供給你一些指標(biāo)和詳細(xì)的信息,無(wú)痛的增加你的代碼的覆蓋率。
下面是一個(gè)簡(jiǎn)易的覆蓋率報(bào)告:
我們可以詳細(xì)的看到每個(gè)文件夾的每個(gè)文件的代碼是否被測(cè)試覆蓋。歸功于grunt+karma的集成,這個(gè)報(bào)告是實(shí)時(shí)更新的。我們可以在每一個(gè)文件中一行一行的檢查那一塊帶按摩沒有被測(cè)試。這樣能使測(cè)試變得更加的簡(jiǎn)單。
100% test coverage 不代表你的代碼就沒有BUG了,但它代表這代碼質(zhì)量的提高!karma+grunt的集成特別的簡(jiǎn)單,karma有一套「插件」系統(tǒng),它允許我們通過配置karma-unit.js文件來(lái)外掛fantastic Istanbul 代碼覆蓋率檢測(cè)工具。只要配置一下文件,媽媽就再也不用擔(dān)心我的代碼覆蓋率了。
Add coverage to karma# add the necessary node_modules npm install karma-coverage --save-dev
現(xiàn)在將新的設(shè)置更新到kamar的配置文件中
// here we specify which of the files we want to appear in the coverage report preprocessors: { "src/angular-stepper.js": ["coverage"] }, // add the coverage plugin plugins: [ "karma-jasmine", "karma-firefox-launcher", "karma-chrome-launcher", "karma-coverage"], // add coverage to reporters reporters: ["dots", "coverage"], // tell karma how you want the coverage results coverageReporter: { type : "html", // where to store the report dir : "coverage/" }
更多覆蓋率的設(shè)置請(qǐng)看這里:https://github.com/karma-runner/karma-coverage
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78078.html
摘要:可發(fā)布這一部分會(huì)在下一章管理對(duì)子項(xiàng)目引用中詳細(xì)說(shuō)明??偨Y(jié)本文總結(jié)了多項(xiàng)目共享子項(xiàng)目工程化方面的一些實(shí)踐,并不涉及到復(fù)雜的代碼,主要涉及到的概念,使用進(jìn)行包管理,使用作為自動(dòng)化工具等工程化的知識(shí)。 背景 公司的產(chǎn)品線涵蓋多個(gè)產(chǎn)品,這些產(chǎn)品中會(huì)有一些相同的功能,如登錄,認(rèn)證等,為了保持這些功能在各個(gè)產(chǎn)品中的一致性,我們?cè)诟鱾€(gè)產(chǎn)品中維護(hù)一份相同的代碼。這帶來(lái)了很大的不便:當(dāng)出現(xiàn)新的需求時(shí),不...
angular2.0 學(xué)習(xí)筆記 ### 1.angular-cli 常用命令記錄 詳細(xì)教程 angular cli官網(wǎng) 有,這里不詳細(xì)說(shuō)明,感興趣的可以自行到官網(wǎng)看,一下僅記錄本人到學(xué)習(xí)過程常用到的命令 1.創(chuàng)建項(xiàng)目 ng new ng new project-name exp: ng new my-app 2.啟動(dòng)項(xiàng)目 ng serve 參數(shù)名 類型 默認(rèn)值 作用 exp ...
摘要:自定義指令中有很多內(nèi)置指令,一般都是以開頭的比如等等。本文介紹的自定義指令的用法。該參數(shù)的意思是替換指令的內(nèi)容,更改上面的例子。將屬性綁定到父控制器的域中學(xué)習(xí)概念多指令中的參數(shù)中增加了的值和的點(diǎn)擊函數(shù)。 自定義指令 angularjs中有很多內(nèi)置指令,一般都是以ng開頭的;比如:ng-app,ng-click,ng-repeat等等。本文介紹angularjs的自定義指令的用法。 指令...
摘要:自定義指令中有很多內(nèi)置指令,一般都是以開頭的比如等等。本文介紹的自定義指令的用法。該參數(shù)的意思是替換指令的內(nèi)容,更改上面的例子。將屬性綁定到父控制器的域中學(xué)習(xí)概念多指令中的參數(shù)中增加了的值和的點(diǎn)擊函數(shù)。 自定義指令 angularjs中有很多內(nèi)置指令,一般都是以ng開頭的;比如:ng-app,ng-click,ng-repeat等等。本文介紹angularjs的自定義指令的用法。 指令...
閱讀 1388·2021-10-08 10:04
閱讀 2713·2021-09-22 15:23
閱讀 2735·2021-09-04 16:40
閱讀 1187·2019-08-29 17:29
閱讀 1508·2019-08-29 17:28
閱讀 3004·2019-08-29 14:02
閱讀 2235·2019-08-29 13:18
閱讀 860·2019-08-23 18:35