# 15測試
### 使用 Jasmine 測試
### 問題
假如你正在使用 CoffeeScript 寫一個簡單地計算器,并且想要驗證其功能是否與預期一致。可以使用 [Jasmine](http://jasmine.github.io/) 測試框架。
### 討論
在使用 Jasmine 測試框架時,你要在一個參數(spec)文檔中寫測試,文檔描述的是代碼需要測試的預期功能。
例如,我們希望計算器可以實現加法和減法的功能,并且可以正確進行正數和負數的運算。我們的 spec 文檔如下列所示。
~~~
# calculatorSpec.coffee
?
describe 'Calculator', ->
it 'can add two positive numbers', ->
calculator = new Calculator()
result = calculator.add 2, 3
expect(result).toBe 5
?
it 'can handle negative number addition', ->
calculator = new Calculator()
result = calculator.add -10, 5
expect(result).toBe -5
?
it 'can subtract two positive numbers', ->
calculator = new Calculator()
result = calculator.subtract 10, 6
expect(result).toBe 4
?
it 'can handle negative number subtraction', ->
calculator = new Calculator()
result = calculator.subtract 4, -6
expect(result).toBe 10
~~~
### 配置 Jasmine
在你運行測試之前,必須要先下載并配置 Jasmine。包括:1.下載最新的 [Jasmine](https://github.com/pivotal/jasmine/tree/master/dist) 壓縮文件;2.在你的項目工程中創建一個 spec 以及一個 spec/jasmine 目錄;3.將下載的 Jasmine 文件解壓到 spec/jasmine 目錄中;4.創建一個測試流
### 創建測試流
Jasmine 可以使用 spec runner 的 HTML 文檔在 web 瀏覽器中運行你的測試。 spec runner 是一個簡單地 HTML 頁面,連接著 Jasmine 以及你的代碼所需要的必要的 JavaScript 和 CSS 文件。示例如下。
~~~
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
2 "http://www.w3.org/TR/html4/loose.dtd">
3 <html>
4 <head>
5 <title>Jasmine Spec Runner</title>
6 <link rel="shortcut icon" type="image/png" href="spec/jasmine/jasmine_favicon.png">
7 <link rel="stylesheet" type="text/css" href="spec/jasmine/jasmine.css">
8 <script src="http://code.jquery.com/jquery.min.js"></script>
9 <script src="spec/jasmine/jasmine.js"></script>
10 <script src="spec/jasmine/jasmine-html.js"></script>
11 <script src="spec/jasmine/jasmine-jquery-1.3.1.js"></script>
12
13 <!-- include source files here... -->
14 <script src="js/calculator.js"></script>
15
16 <!-- include spec files here... -->
17 <script src="spec/calculatorSpec.js"></script>
18
19 </head>
20
21 <body>
22 <script type="text/javascript">
23 (function() {
24 var jasmineEnv = jasmine.getEnv();
25 jasmineEnv.updateInterval = 1000;
26
27 var trivialReporter = new jasmine.TrivialReporter();
28
29 jasmineEnv.addReporter(trivialReporter);
30
31 jasmineEnv.specFilter = function(spec) {
32 return trivialReporter.specFilter(spec);
33 };
34
35 var currentWindowOnload = window.onload;
36
37 window.onload = function() {
38 if (currentWindowOnload) {
39 currentWindowOnload();
40 }
41 execJasmine();
42 };
43
44 function execJasmine() {
45 jasmineEnv.execute();
46 }
47
48 })();
49 </script>
50 </body>
51 </html>
~~~
此 spec runner 可以在 GitHub [gist](https://gist.github.com/2623232) 上下載。
使用 SpecRunner.html ,只是簡單地參考你編譯后的 JavaScript 文件,并且在 jasmine.js 以及其依賴項后編譯的測試文件。
在上述示例中,我們在第 14 行包含了尚待開發的 calculator.js 文件,在第17行編譯了 calculatorSpec.js 文件。
### **運行測試**
要運行我們的測試,只需要簡單地在 web 瀏覽器中打開 SpecRunner.html 頁面。在我們的示例中可以看到 4 個失敗的 specs 共 8 個失敗情況(如下)。

**圖片 15.1** Alt text
看來我們的測試是失敗的,因為 jasmine 無法找到 Calculator 變量。那是因為它還沒有被創建。現在讓我們來創建一個新文件命名為 js/calculator.coffee 。
~~~
# calculator.coffee
?
?
window.Calculator = class Calculator
~~~
編譯 calculator.coffee 并刷新瀏覽器來重新運行測試組。

**圖片 15.2** Alt text
現在我們還有4個失敗而不是原來的8個了,只用一行代碼便做出了50%的改進。
### **測試通過**
實現我們的方法來看是否可以通過測試。
~~~
# calculator.coffee
?
?
window.Calculator = class Calculator
add: (a, b) ->
a + b
?
subtract: (a, b) ->
a - b
~~~
當我們刷新頁面時可以看到全部通過。

**圖片 15.3** Alt text
### **重構測試**
既然測試全部通過了,我們應看一看我們的代碼或測試是否可以被重構。
在我們的 spec 文件中,每個測試都創建了自己的 calculator 實例。這會使我們的測試相當的重復,特別是對于大型的測試套件。理想情況下,我們應該考慮將初始化代碼移動到每次測試之前運行。
幸運的是 Jasmine 擁有一個 beforeEach 函數,就是為了這一目的設置的。
~~~
describe 'Calculator', ->
calculator = null
?
beforeEach ->
calculator = new Calculator()
?
it 'can add two positive numbers', ->
result = calculator.add 2, 3
expect(result).toBe 5
?
it 'can handle negative number addition', ->
result = calculator.add -10, 5
expect(result).toBe -5
?
it 'can subtract two positive numbers', ->
result = calculator.subtract 10, 6
expect(result).toBe 4
?
it 'can handle negative number subtraction', ->
result = calculator.subtract 4, -6
expect(result).toBe 10
~~~
當我們重新編譯我們的 spec 然后刷新瀏覽器,可以看到測試仍然全部通過。

**圖片 15.4** Alt text
### 使用 Nodeunit 測試
### 問題
假如你正在使用 CoffeeScript 并且想要驗證功能是否與預期一致,便可以決定使用 [Nodeunit](https://github.com/caolan/nodeunit) 測試框架。
### 討論
Nodeunit 是一種 JavaScript 對于單元測試庫( Unit Testing libraries )中 xUnit 族的實現,Java, Python, Ruby, Smalltalk 中均可以使用。
當使用 xUnit 族測試框架時,你需要將所需測試的描述預期功能的代碼寫在一個文件中。
例如,我們希望我們的計算器可以進行加法和減法,并且對于正負數均可以正確計算,我們的測試如下。
~~~
# test/calculator.test.coffee
?
Calculator = require '../calculator'
exports.CalculatorTest =
'test can add two positive numbers': (test) ->
calculator = new Calculator
result = calculator.add 2, 3
test.equal(result, 5)
test.done()
?
'test can handle negative number addition': (test) ->
calculator = new Calculator
result = calculator.add -10, 5
test.equal(result, -5)
test.done()
?
'test can subtract two positive numbers': (test) ->
calculator = new Calculator
result = calculator.subtract 10, 6
test.equal(result, 4)
test.done()
?
'test can handle negative number subtraction': (test) ->
calculator = new Calculator
result = calculator.subtract 4, -6
test.equal(result, 10)
test.done()
~~~
### **安裝 Nodeunit**
在可以運行你的測試之前,你必須先安裝 Nodeunit :
首先創建一個 package.json 文件
~~~
{
"name": "calculator",
"version": "0.0.1",
"scripts": {
"test": "./node_modules/.bin/nodeunit test"
},
"dependencies": {
"coffee-script": "~1.4.0",
"nodeunit": "~0.7.4"
}
}
~~~
接下來從一個終端運行。
~~~
$ npm install
~~~
### **運行測試**
使用代碼行可以簡便地運行測試文件:
~~~
$ npm test
~~~
測試失敗,因為我們并沒有 calculator.coffee
~~~
suki@Yuzuki:nodeunit_testing (master)$ npm test
npm WARN package.json calculator@0.0.1 No README.md file found!
?
> calculator@0.0.1 test /Users/suki/tmp/nodeunit_testing
> ./node_modules/.bin/nodeunit test
?
?
/Users/suki/tmp/nodeunit_testing/node_modules/nodeunit/lib/nodeunit.js:72
if (err) throw err;
^
Error: ENOENT, stat '/Users/suki/tmp/nodeunit_testing/test'
npm ERR! Test failed. See above for more details.
npm ERR! not ok code 0
~~~
我們創建一個簡單文件
~~~
# calculator.coffee
?
?
class Calculator
?
module.exports = Calculator
~~~
并且重新運行測試套件。
~~~
suki@Yuzuki:nodeunit_testing (master)$ npm test
npm WARN package.json calculator@0.0.1 No README.md file found!
?
> calculator@0.0.1 test /Users/suki/tmp/nodeunit_testing
> ./node_modules/.bin/nodeunit test
?
?
calculator.test
? CalculatorTest - test can add two positive numbers
?
TypeError: Object #<Calculator> has no method 'add'
...
?
? CalculatorTest - test can handle negative number addition
?
TypeError: Object #<Calculator> has no method 'add'
...
?
? CalculatorTest - test can subtract two positive numbers
?
TypeError: Object #<Calculator> has no method 'subtract'
...
?
? CalculatorTest - test can handle negative number subtraction
?
TypeError: Object #<Calculator> has no method 'subtract'
...
?
?
FAILURES: 4/4 assertions failed (31ms)
npm ERR! Test failed. See above for more details.
npm ERR! not ok code 0
~~~
### **通過測試**
讓我們對方法進行實現來觀察測試是否可以通過。
~~~
# calculator.coffee
?
?
class Calculator
?
add: (a, b) ->
a + b
?
subtract: (a, b) ->
a - b
?
module.exports = Calculator
~~~
當我們重新運行測試時可以看到全部通過:
~~~
suki@Yuzuki:nodeunit_testing (master)$ npm test
npm WARN package.json calculator@0.0.1 No README.md file found!
?
> calculator@0.0.1 test /Users/suki/tmp/nodeunit_testing
> ./node_modules/.bin/nodeunit test
?
?
calculator.test
? CalculatorTest - test can add two positive numbers
? CalculatorTest - test can handle negative number addition
? CalculatorTest - test can subtract two positive numbers
? CalculatorTest - test can handle negative number subtraction
?
OK: 4 assertions (27ms)
~~~
### **重構測試**
既然測試全部通過,我們應看一看我們的代碼或測試是否可以被重構。
在我們的測試文件中,每個測試都創建了自己的 calculator 實例。這會使我們的測試相當的重復,特別是對于大型的測試套件。理想情況下,我們應該考慮將初始化代碼移動到每次測試之前運行。
通常在其他的 xUnit 庫中,Nodeunit 會提供一個 setUp(以及 tearDown )功能會在測試前調用。
~~~
Calculator = require '../calculator'
?
exports.CalculatorTest =
?
setUp: (callback) ->
@calculator = new Calculator
callback()
?
'test can add two positive numbers': (test) ->
result = @calculator.add 2, 3
test.equal(result, 5)
test.done()
?
'test can handle negative number addition': (test) ->
result = @calculator.add -10, 5
test.equal(result, -5)
test.done()
?
'test can subtract two positive numbers': (test) ->
result = @calculator.subtract 10, 6
test.equal(result, 4)
test.done()
?
'test can handle negative number subtraction': (test) ->
result = @calculator.subtract 4, -6
test.equal(result, 10)
test.done()
~~~
我們可以重新運行測試,仍然可以全部通過。