摘要:翻譯自在這篇文章中,我將詳述如何給我們上周開發的做單元測試的過程。單元測試是一種測試你的項目中每個最小單元代碼的藝術,是使你的程序思路清晰的基礎。
第一次翻譯技術文章,肯定很多語句很生疏,有看官的話就見諒,沒有的話也沒人看的到這句話。。
翻譯自:Unit Testing an AngularJS Directive
在這篇文章中,我將詳述如何給我們上周開發的stepper directive做單元測試的過程。下周會講到如何使用Github和Bower進行組件分離。
單元測試是一種測試你的項目中每個最小單元代碼的藝術,是使你的程序思路清晰的基礎。一旦所有的測試通過,這些零散的單元組合在一起也會運行的很好,因為這些單元的行為已經被獨立的驗證過了。
單元測試能夠避免你的代碼出現回歸性BUG,提高代碼的質量和可維護性,使你的代碼在代碼庫中是可信賴的,從而提高團隊合作的質量,使重構變得簡單和快樂: )
單元測試的另一個用處是當你發現了一個新的BUG,你可以為這個BUG寫一個單元測試,當你修改了你的代碼,使這個測試可以PASS了的時候,就說明這個BUG已經被修復了。
AngularJS最好的小伙伴兒KarmaJS test runner(一個能夠在瀏覽器中運行測試同時生成結果日志的Node.js server)還有 Jasmine(定義了你的測試和斷言的語法的庫)。我們使用Grunt-karma將karma集成在我們經典且繁重的grunt 工作流中,然后在瀏覽器中運行測試。這里值得注意的是,karma可以將測試運行在遠程的云瀏覽器中,比如SauceLabs和BrowserStack。
AngularJS是將是經過了嚴密地測試的,所以趕緊給自己點個贊,現在就開始寫測試吧!
術語:
在我們進行下一步之前有一些術語需要說明:
spec: 你想要測試的代碼的說明,包括一個或多個測試條件。spec應該覆蓋所有預期行為。
test suite: 一組測試的集合,定義在Jasmine提供的describe語句塊中,語句塊是可以嵌套的。
test: 測試說明,寫在Jasmin提供的it語句塊中,以一個或者多個期望值結束(譯者按:也就是說,一個it語句塊中,一定要有一個以上的期望值)。
actual: 在你的期望中要被測試的值。
expected value: 針對測試出的真實值做比較的期望值。(原文:this is the value you test the actual value against.)
matcher: 一個返回值為Boolean類型的函數,用于比較真實值跟期望值。結果返回給jasmine,比如toEqual,toBeGreatherThan,toHaveBeenCalledWith... 你也可以定義你自己的matcher。
expectation: 使用expect函數測試一個值,得到它的返回值,expectation是與一個得到期望值的matcher函數鏈接的。(原文: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」(不會翻譯)服務,你可以制造一些假數據或方法來替代程序真正運行時所產生的數據。
這有一個spec文件的例子:
測試環境搭建
// a test suite (group of tests) //一組測試 describe("sample component test", function() { // a single test //多帶帶的測試 it("ensure addition is correct", function() { // sample expectation // 簡單的期望 expect(1+1).toEqual(2); // `--- the expected value (2) 期望值是2 // `--- the matcher method (equality) toEqual方法就是matcher函數 // `-- the actual value (2) 真實值是2 }); // another test // 另一個測試 it("ensure substraction is correct", function() { expect(1-1).toEqual(0); }); });
將grunt-karma添加到你項目的依賴中
npm install grunt-karma --save -dev
創建一個karma-unit.js文件
這里是一個karma-unit文件的例子
這個文件定義了如下內容:
* 將要被加載到瀏覽器進行測試的JS文件。通常情況下,不僅項目用的庫和項目本身的文件需要包含在內,你所要測試的文件和mock文件也要在這里加載。
* 你想將測試運行在哪款瀏覽器中。
* 怎樣接收到測試結果,是命令行里還是在瀏覽器中...?
* 可選插件。
以下是files這一項的例子:
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在里面,如果你需要它幫助你編寫測試代碼(更強大的選擇器,CSS測試,尺寸計算…)
將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"] } }
然后創建 angular-stepper.spec.js文件,將上面寫的簡單的測試代碼粘貼進來。這時你就可以輕松運行grunt karma任務去觀察你的測試在瀏覽器中運行并且在命令行中生成測試報告。
.... 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
上面有四個點,每個點都代表一個成功的測試,這時你可以看到,兩個測試分別運行在我們配置的兩個瀏覽器中了。
哦也~
那么接下來,讓我們寫一些真正的測試代碼吧: )
給directive編寫單元測試為我們的組件所編寫的一組單元測試,又叫做spec的東西,不僅應該覆蓋我們所要測試的組件的所有預期行為,還要將邊緣情況覆蓋到(比如不合法的輸入、服務器的異常狀況)。
下面展示的angular-stepper組件的測試集的精華部分,完整版點這里。我們對這樣一個組件的測試非常簡單,不需要假數據。唯一比較有技巧性的是,我們將我們的directive包含在了一個form表單下,這樣能夠在使用ngModelController和更新表單驗證正確性的情況下正確的運行測試。(注:此處的內容需要讀angular-stepper那個組件的文件才能懂為何要將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.)
"; //原文最后一個標簽是感覺是筆誤。 // 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 方法對于生成指令是必要的。 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 在跑測試之前將你要測試的模塊引入進來 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的依賴注入將我們所需要的服務注入進去 // here we need $rootScope to create a new scope 需要用$rootScope新建一個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 = "
一些需要注意的點:
在要被測試的scope中,一個directive需要被compiled(譯者注:也就是上面代碼中的$compile(tpl)(scope);這句話在做的事情)。
一個非隔離scope可以通過element.scope()方法訪問到。
一個隔離的scope可以通過element.isolateScope()方法訪問到。
在一個真正的angular應用中,$digest方法是angular通過各種事件(click,inputs,requests...)的反應自動調用的。自動化測試不是以真實的用戶事件為基礎的,所以我們需要手動的調用$digest方法($digest方法負責更新所有數據綁定)。
額外福利 #1: 實時測試多虧了grunt,當我們的文件改動的時候,可以自動的進行測試。
如果你想在你的代碼有任何改動的時候都進行一次測試,只要將一段代碼加入到grunt的watch任務中就行。
js: { files: ["src/*.js"], tasks: ["karma:unit:run", "build"] },
你也可以將grunt的默認任務設置成這樣:
grunt.registerTask("default", ["karma:unit", "connect", "watch"]);
設置完后,運行grunt,就可以實時的在內置的server中跑測試了。
額外福利 #2:添加測試覆蓋率報告作為開發者,我們希望以靠譜的數據作為依據,我們也希望持續的改進自己的代碼。"coverage"指的是你的測試代碼的覆蓋率,它可以提供給你一些指標和詳細的信息,無痛的增加你的代碼的覆蓋率。
下面是一個簡易的覆蓋率報告:
我們可以詳細的看到每個文件夾的每個文件的代碼是否被測試覆蓋。歸功于grunt+karma的集成,這個報告是實時更新的。我們可以在每一個文件中一行一行的檢查那一塊帶按摩沒有被測試。這樣能使測試變得更加的簡單。
100% test coverage 不代表你的代碼就沒有BUG了,但它代表這代碼質量的提高!karma+grunt的集成特別的簡單,karma有一套「插件」系統,它允許我們通過配置karma-unit.js文件來外掛fantastic Istanbul 代碼覆蓋率檢測工具。只要配置一下文件,媽媽就再也不用擔心我的代碼覆蓋率了。
Add coverage to karma# add the necessary node_modules npm install karma-coverage --save-dev
現在將新的設置更新到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/" }
更多覆蓋率的設置請看這里:https://github.com/karma-runner/karma-coverage
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78078.html
摘要:可發布這一部分會在下一章管理對子項目引用中詳細說明。總結本文總結了多項目共享子項目工程化方面的一些實踐,并不涉及到復雜的代碼,主要涉及到的概念,使用進行包管理,使用作為自動化工具等工程化的知識。 背景 公司的產品線涵蓋多個產品,這些產品中會有一些相同的功能,如登錄,認證等,為了保持這些功能在各個產品中的一致性,我們在各個產品中維護一份相同的代碼。這帶來了很大的不便:當出現新的需求時,不...
angular2.0 學習筆記 ### 1.angular-cli 常用命令記錄 詳細教程 angular cli官網 有,這里不詳細說明,感興趣的可以自行到官網看,一下僅記錄本人到學習過程常用到的命令 1.創建項目 ng new ng new project-name exp: ng new my-app 2.啟動項目 ng serve 參數名 類型 默認值 作用 exp ...
摘要:自定義指令中有很多內置指令,一般都是以開頭的比如等等。本文介紹的自定義指令的用法。該參數的意思是替換指令的內容,更改上面的例子。將屬性綁定到父控制器的域中學習概念多指令中的參數中增加了的值和的點擊函數。 自定義指令 angularjs中有很多內置指令,一般都是以ng開頭的;比如:ng-app,ng-click,ng-repeat等等。本文介紹angularjs的自定義指令的用法。 指令...
摘要:自定義指令中有很多內置指令,一般都是以開頭的比如等等。本文介紹的自定義指令的用法。該參數的意思是替換指令的內容,更改上面的例子。將屬性綁定到父控制器的域中學習概念多指令中的參數中增加了的值和的點擊函數。 自定義指令 angularjs中有很多內置指令,一般都是以ng開頭的;比如:ng-app,ng-click,ng-repeat等等。本文介紹angularjs的自定義指令的用法。 指令...
閱讀 1377·2021-10-08 10:04
閱讀 2681·2021-09-22 15:23
閱讀 2724·2021-09-04 16:40
閱讀 1172·2019-08-29 17:29
閱讀 1492·2019-08-29 17:28
閱讀 2988·2019-08-29 14:02
閱讀 2221·2019-08-29 13:18
閱讀 838·2019-08-23 18:35