AngularJs directive 的单元测试要领

第一次翻译技术文章,肯定许多语句很陌生,有看官的话就包涵,没有的话也没人看的到这句话。。

翻译自: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能够将测试运转在长途的云浏览器中,比方SauceLabsBrowserStack

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.)


// 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 = '<div rn-stepper ng-model="testModel"></div></form>'; tpl = '<form name="form">' + tpl + '</form>'; //原文末了一个标签是</tpl>觉得是笔误。 // 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('<div rn-stepper min="testMin" ng-model="testModel"></div>'); // 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('<div rn-stepper min="40" ng-model="testModel"></div>'); 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... });

一些须要注重的点:

在要被测试的scope中,一个directive须要被compiled(译者注:也就是上面代码中的$compile(tpl)(scope);这句话在做的事变)。
一个非断绝scope能够经由过程element.scope()要领访问到。
一个断绝的scope能够经由过程element.isolateScope()要领访问到。

为啥我在转变一个Model的值的时刻须要挪用scope.$digest()要领?

在一个真正的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”指的是你的测试代码的掩盖率,它能够供应给你一些目标和细致的信息,无痛的增添你的代码的掩盖率。

下面是一个浅易的掩盖率报告:

《AngularJs directive 的单元测试要领》

我们能够细致的看到每一个文件夹的每一个文件的代码是不是被测试掩盖。归功于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

    原文作者:Moe
    原文地址: https://segmentfault.com/a/1190000000486421
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞