Automated testing with mocha

自動化測試是工程師基本要求。

Why we need tests?

當撰寫一個函式,我們會預期產出的結果,我們可能會帶入不同參數用 console 來檢視結果,但若有錯誤修改後,又要一一測試之前帶入的參數,所以測試跟開發是分開的,測試可以很容易執行,檢查主要使用沒問題。

Behavior Driven Development (BDD)

行為驅動的開發被用在許多專案中,BDD 包含 3 件事測試、文件、範例。

Development of “pow”: the spec

我們要寫一個函式,pow(x, n) 該函式將產出 x 的 n 次方,在創建函式之前可以描述該函式的功能有 3 個規範。

  1. describe("title", function() { ... }) 描述函式名稱。

  2. it("title", function() { ... }) 描述函式功能

  3. assert.equal(value1, value2) 帶入參數測試函數的產出是否正確

The development flow

  1. 撰寫規範

  2. 撰寫基本函式可以跑測試

  3. 執行測試框架 mocha ,檢測函示是否正確

  4. 增加更多案例到測試中

  5. 測試失敗修改函式

  6. 重複 3 ~ 5 步驟

The spec in action

測試主要用到 3 個 librarues。

  • mocha 主要執行的框架 包含 descibe it 等函數。

  • chai 有很多 assertions 函式,範例使用 assert.equal

  • Sinon 用來監視函數模擬內建函式。

<!DOCTYPE html>
<html>
<head>
  <!-- add mocha css, to show results -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.css">
  <!-- add mocha framework code -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/3.2.0/mocha.js"></script>
  <script>
    mocha.setup('bdd'); // minimal setup
  </script>
  <!-- add chai -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/chai/3.5.0/chai.js"></script>
  <script>
    // chai has a lot of stuff, let's make assert global
    let assert = chai.assert;
  </script>
</head>

<body>

  <script>
    function pow(x, n) {
      /* function code is to be written, empty now */
    }
  </script>

  <!-- the script with tests (describe, it...) -->
  <script src="test.js"></script>

  <!-- the element with id="mocha" will contain test results -->
  <div id="mocha"></div>

  <!-- run tests! -->
  <script>
    mocha.run();
  </script>
</body>

</html>

頁面分為 5 個部分

  1. head 引入 libraries

  2. script 撰寫測試的函示

  3. 測試程式碼寫在 test.js

  4. <div id='mocha'> 用來輸出 mocha 執行的結果

  5. mocha.run() 執行 mocha

Improving the spec

增加更多情況到測試中,一個測試只檢查一個功能。

// method1
describe("pow", function() {

  it("raises to n-th power", function() {
    assert.equal(pow(2, 3), 8);
    assert.equal(pow(3, 4), 81);
  });

});

// method2 better
describe("pow", function() {

  it("2 raised to power 3 is 8", function() {
    assert.equal(pow(2, 3), 8);
  });

  it("3 raised to power 3 is 27", function() {
    assert.equal(pow(3, 3), 27);
  });

});

Improving the implementation

function pow(x, n) {
  let result = 1;

  for (let i = 0; i < n; i++) {
    result *= x;
  }

  return result;
}

// test
describe("pow", function() {

  function makeTest(x) {
    let expected = x * x * x;
    it(`${x} in the power 3 is ${expected}`, function() {
      assert.equal(pow(x, 3), expected);
    });
  }

  for (let x = 1; x <= 5; x++) {
    makeTest(x);
  }

});

Nested describe

makeTest() 只用在 for 迴圈內,因此將他們綁在一起,下一個測試不會在用到。

describe("pow", function() {

  describe("raises x to power 3", function() {

    function makeTest(x) {
      let expected = x * x * x;
      it(`${x} in the power 3 is ${expected}`, function() {
        assert.equal(pow(x, 3), expected);
      });
    }

    for (let x = 1; x <= 5; x++) {
      makeTest(x);
    }

  });

  // ... more tests to follow here, both describe and it can be added
});
describe("test", function() {

  before(() => alert("Testing started – before all tests"));
  after(() => alert("Testing finished – after all tests"));

  beforeEach(() => alert("Before a test – enter a test"));
  afterEach(() => alert("After a test – exit a test"));

  it('test 1', () => alert(1));
  it('test 2', () => alert(2));

});

// result
Testing started – before all tests (before)
Before a test – enter a test (beforeEach)
1
After a test – exit a test   (afterEach)
Before a test – enter a test (beforeEach)
2
After a test – exit a test   (afterEach)
Testing finished – after all tests (after)

Extending the spec

增加規範,程式碼出現錯誤,修改程式碼。

// add spec
describe("pow", function() {

  // ...

  it("for negative n the result is NaN", function() {
    assert.isNaN(pow(2, -1));
  });

  it("for non-integer n the result is NaN", function() {
    assert.isNaN(pow(2, 1.5));
  });

});

// improve pow(x ,n)
function pow(x, n) {
  if (n < 0) return NaN;
  if (Math.round(n) != n) return NaN;

  let result = 1;

  for (let i = 0; i < n; i++) {
    result *= x;
  }

  return result;
}

  • assert.equal(value1, value2) – checks the equality value1 == value2.

  • assert.strictEqual(value1, value2) – checks the strict equality value1 === value2.

  • assert.notEqual, assert.notStrictEqual – inverse checks to the ones above.

  • assert.isTrue(value) – checks that value === true

  • assert.isFalse(value) – checks that value === false

  • …the full list is in the docs

Last updated