敏捷軟體開發中,最重要實踐的就是測試驅動開發,在單元測試層面,我們試著實現一個重要的指標就是測試覆蓋率。測試覆蓋率衡量我們的程式碼是否已經全部被測試到了。
但是指標本身不是目的,借助測試覆蓋率檢查,我們希望發現那些未被測試覆蓋的程式碼,從而去思考如何測試那些程式碼的邏輯,進而更好的設計重構程式碼,讓程式碼有更高的質量[1]。
#談到測試,剛好最近在看《數學之美》,書中談到的一段關於資訊的話。我們要把程式碼的行為從不確定性,變成確定性,也是一樣。從黑盒變成白盒,沒有什麼神奇的力量,唯有提供足夠的信息,而測試中的斷言就是信息,測試覆蓋率也是信息,測試覆蓋率可以認為是一種間接的信息,可以消除一部分不確定性,而充分的斷言,則提供了更直接的資訊。加上測試覆蓋率檢查,就能夠提供足夠的信息,來斷言程式碼的行為是否符合期望。
Istanbul
是JavaScript
程式的程式碼覆蓋率工具,以土耳其最大城市伊斯坦堡命名。 Istanbul會對程式碼轉換,產生語法樹,然後在對應位置注入統計程式碼,執行之後根據注入的全域變數的值,統計程式碼執行的次數;在對程式碼的轉換完成之後,Istanbul會呼叫test runner
,例如mocha
,執行轉換之後的程式碼的測試,產生測試報告。
Mocha
是一種測試框架,也就是執行測試的工具,類似Jasmine、Karma和Ava。跟JUnit的註解一樣,mocha作為執行器,用descibe
和it
方法,來定義test suit,為不同的測試分組。 mocha本身並沒有提供assert
斷言,所以要提供更有表現力的斷言,可以搭配chai
使用,當然也可以使用nodejs提供的assert模組
。
在我們的程式碼中,總是會有一些複雜的邏輯或依賴io、網路的非同步程式碼,用直接的方法難以測試,這時可以透過sinon
簡化複雜程式碼的測試。 Sinon透過創建Test Double也就是測試替身
,將我們程式碼中依賴的一些函數或類,替換成測試替身,而我們可以對測試替身的行為進行設置,模擬我們的程式碼所需的結果,從而讓難以測試的程式碼邏輯被執行。
mocha和istanbul可以全域安裝,也可以只在目前專案安裝。
<code class="q">npm install --<span class="hljs-built_in">save-<span class="hljs-built_in">dev mocha chai sinon istanbul</span></span></code>
安裝完成之後,在package.json
檔案的scripts下,新增執行測試和測試覆蓋率檢查的命令
<code class="json">{ ... <span class="hljs-attr">"scripts":{ <span class="hljs-attr">"coverage": <span class="hljs-string">"istanbul cover _mocha -- -R spec --timeout 5000 --recursive", <span class="hljs-attr">"coverage:check": <span class="hljs-string">"istanbul check-coverage", } ... }</span></span></span></span></span></code>
執行npm run coverage
和npm run coverage:check
,就可以產生測試報告,前者產生測試報告,後者則是檢查測試覆蓋率是否達到要求。
istanbul相关的执行参数,可以在命令行下执行时传递参数来制定,也可以在yaml格式的.istanbul.yml
文件中配置。简单贴出一些重要的配置项
<code class="yaml"><span class="hljs-attr">instrumentation: <span class="hljs-attr"> root: . <span class="hljs-comment"># 执行的根目录 <span class="hljs-attr"> extensions: <span class="hljs-bullet"> - .js <span class="hljs-comment"># 检查覆盖率的文件扩张名 <span class="hljs-attr"> excludes: [<span class="hljs-string">'**/benchmark/**'] ... ... <span class="hljs-attr">reporting: <span class="hljs-attr"> print: summary <span class="hljs-attr"> reports: [lcov, text, html, text-summary] <span class="hljs-comment"># 生成报告的格式 <span class="hljs-attr"> dir: ./coverage <span class="hljs-comment"># 生成报告保存的目录 <span class="hljs-attr"> watermarks: <span class="hljs-comment"># 在不同覆盖率下会显示使用不同颜色 <span class="hljs-attr"> statements: [<span class="hljs-number">80, <span class="hljs-number">95] ... ... <span class="hljs-attr">check: <span class="hljs-attr"> global: <span class="hljs-attr"> statements: <span class="hljs-number">100 <span class="hljs-attr"> branches: <span class="hljs-number">100 <span class="hljs-attr"> lines: <span class="hljs-number">100 <span class="hljs-attr"> functions: <span class="hljs-number">100</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code>
最后的check是项目要通过覆盖率检查需要达到的测试覆盖率,测试覆盖率包括四个维度,分别是语句覆盖率、分支覆盖率、行覆盖率和函数覆盖率。如果达不到设定的指标,在执行的时候会报错,项目的测试就无法通过自动化的持续集成。
敏捷软件开发中的测试驱动开发,意在通过先写测试,根据调用者的契约,设计如何实现代码,从而写出更加容易测试的代码,提高代码的质量。也是我们练习测试的应该考虑的方向。
利用chai提供的expect断言,我们可以用BDD的方式,写出更加符合代码预期行为的测试用例。
<code class="javascript"><span class="hljs-keyword">var chai = <span class="hljs-built_in">require(<span class="hljs-string">'chai') chai.should() <span class="hljs-keyword">var expect = chai.expect <span class="hljs-keyword">var assert = chai.assert describe(<span class="hljs-string">'basic test', <span class="hljs-function"><span class="hljs-keyword">function (<span class="hljs-params">) { describe(<span class="hljs-string">'simple', <span class="hljs-function"><span class="hljs-keyword">function (<span class="hljs-params">) { it(<span class="hljs-string">'data check', <span class="hljs-function"><span class="hljs-keyword">function (<span class="hljs-params">) { <span class="hljs-keyword">var data = { <span class="hljs-attr">name: <span class="hljs-string">"test" } assert.isNotNull(data, <span class="hljs-string">'data should not be null') expect(data).to.be.an(<span class="hljs-string">'object') expect(data).to.have.all.keys([<span class="hljs-string">'name']) expect(data).to.deep.include({<span class="hljs-attr">name: <span class="hljs-string">'test'}); }); }); });</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code>
<code class="javascript">... 同上 ... var sinon = <span class="hljs-built_in">require(<span class="hljs-string">'sinon') <span class="hljs-keyword">var fs = <span class="hljs-built_in">require(<span class="hljs-string">'fs') describe(<span class="hljs-string">'sinon', <span class="hljs-function"><span class="hljs-keyword">function (<span class="hljs-params">) { it(<span class="hljs-string">"should mock readFile", <span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">done){ sinon.stub(fs, <span class="hljs-string">'readFile').callsFake(<span class="hljs-function"><span class="hljs-keyword">function (<span class="hljs-params">path, callback) { callback(<span class="hljs-keyword">new <span class="hljs-built_in">Error(<span class="hljs-string">'read error')) }) fs.readFile(<span class="hljs-string">"any file path", <span class="hljs-function"><span class="hljs-keyword">function(<span class="hljs-params">err,data){ assert.isNotNull(err) done() }) assert(fs.readFile.calledOnce) }); });</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code>
在mocha测试框架中,如果我们调用的是异步的代码,那么需要显示的调用it回调函数的done方法,告诉mocha异步调用什么时候结束。否则的话,测试会挂起,直到设置的超时时间结束。
Sinon将测试替身分为spy、stub和mock,其中:
本文的讨论篇幅有限,暂时不详细介绍各种sinon的使用方法,以后再通过其他文章专门介绍。
完成所有代码之后,我们可以将代码发布到github,然后使用持续集成工具travis检查代码,将生成的测试报告上传到coverall上,这样就可以在项目中显示项目状态和测试覆盖率的badges。
具体使用方法,可以参看官方网站,使用coveralls需要在项目中安装依赖包npm i -D coveralls
。并且添加package.json执行脚本istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
通常的nodejs项目.travis.yml
配置如下:
<code class="yaml"><span class="hljs-attr">language: node_js <span class="hljs-attr">node_js: <span class="hljs-bullet"> - <span class="hljs-string">"7.6.0" <span class="hljs-attr">install: <span class="hljs-bullet"> - npm install <span class="hljs-attr">script: <span class="hljs-bullet"> - npm test <span class="hljs-attr">after_script: <span class="hljs-bullet"> - npm run coverall</span></span></span></span></span></span></span></span></span></span></code>
原文地址:http://www.51test.space/archives/2543
以上是mocha、chai、sinon和istanbul實現100%單元測試覆蓋率的詳細內容。更多資訊請關注PHP中文網其他相關文章!