yutaponのブログ

javascript界隈の興味あるネタを備忘録的に残しておく場所

javascriptでテスト駆動開発 Mocha+expect.js+testem【後編】

前回はtestemを使って自動でテストが走る環境作りをしました。

今回も引き続きTDDっぽいことしていきます。

addメソッドをテストする

前回作ったcalcモジュールのaddメソッドをテストしていきます。

まずは新しいターミナルを立ちあげて、
プロジェクトのディレクトリに移動してtestemを起動します。

$ cd ~/path/to/my/project
$ testem

これでtestemが立ち上がりました。

では次に、前回作ったテストファイルにテストケースを追記してみます。

// test/calc.test.js

var expect = require('expect.js');
var calc   = require('../lib/calc.js');

describe('add', function(){
  it('1と1を与えたら2を返す', function(){
    var result = calc.add(1, 1);
    expect(result).to.be(2);
  });
  // テストケースを追記
  it('1と2を与えたら4にならない', function(){
    var result = calc.add(1, 2);
    expect(result).not.to.be(4);
  });
});

testemを実行しているので自動でテストが走り、
ターミナルには

✔ 2 tests complete.

と表示されました。

さらにテストケースを追記してみます。

  // 追記
  it('1と"2"を与えたら3を返す', function(){
      var result = calc.add(1, "2");
      expect(result).to.be(3);
  });

testemには何やらエラーのようなものが表示されました。

add 1と"2"を与えたら3を返す
    ✘ Error: expected '12' to equal 3
        at Assertion.assert (/path/to/myapp/node_modules/expect.js/expect.js:99:13)
        at Assertion.be.Assertion.equal (/path/to/myapp/node_modules/expect.js/expect.j
s:200:10)
        at Assertion.(anonymous function) [as be] (/path/to/myapp/node_modules/expect.j
s/expect.js:73:24)
        at Context.<anonymous> (/path/to/myapp/test/calc.test.js:15:22)
        at Test.Runnable.run (/path/to/username/.nave/installed/0.10.5/lib/node_modules/mocha/lib/runnabl
e.js:213:32)

1行目にテストケースの内容、2行目に実際の結果が表示されてます。
テストケースでは結果として3が返ってくることを期待しましたが、
実際のところは文字列の"12"が返ってきました。

これはJavaScriptの仕様で、+演算子は文字列としての結合を優先するためです。

これはバグになるもとなので、今回作るaddメソッドでは
1+"2"を3と返すように修正します。

// lib/calc.js

// 数値型に変換する関数作ってみた
var castToNumber = function(v){
    return typeof v === 'string' ? Number(v) : v;
};

exports.add = function(a, b){  
    return castToNumber(a) + castToNumber(b);
};

testemの画面には、

✔ 3 tests complete.

はい、テストが通りました!


・・ですが、まだ穴があります。
もうすこしテストケースを作成します。

  // テストケース追記
  it('1と{}を与えたら1を返す', function(){
      var result = calc.add(1, {});
      expect(result).to.be(1);
  });

testemは、

add 1と{}を与えたら1を返す
    ✘ Error: expected '1[object Object]' to equal 1

へー、こんな結果になるのか。
ということでコードを修正します。

// 文字列は数値型に変換して、他は0を返すように変更してみた
var castToNumber = function(v){
    if (typeof v === 'number') return v;
    if (typeof v === 'string') {
        return Number(v);
    } else {
        return 0;
    }
};

exports.add = function(a, b){  
    return castToNumber(a) + castToNumber(b);
};

testemの結果は、

✔ 4 tests complete.

全てのテストケースをパスしてグリーンになりました。


テスト駆動っぽいことをしてみて

常にテストが走る環境で開発することでコードに自信を持てるし、
バグが生じてもすぐに発見できるようになります。

テストっていうと軽いニュアンスになってしまいますが、
Railsとかだとspec(仕様)にテストコードを書くので、
テストコードを書くということイコール仕様をコードで表現するってことです。

テストコードを書くときは、

  • describe('〇〇', function(){});
    • 〇〇について説明します。
  • it('〜〜', function(){});
    • それは、〜〜です。

と、仕様を明確にしながら書いていきます。


たとえば、addメソッドは引数を2つとりますが、
引数が1つしか渡されなかった時のふるまいはどうなるでしょう?

テストケースにはまだこの記述がありません。
つまり、試してみないとわからない状態です。

引数が1つしか渡されなかったとき、
実際にはbがundefinedになるので0に変換されますが、
NaNを返すか、Exceptionを返すという選択肢もあります。

他の開発者がこの疑問を思ったとき、まっさきにテストケースに目を通せば
addメソッドのふるまいを理解できることでしょう。


おわりに

前回、今回とテスト回でした。
実際の業務ではガッチリとテスト駆動開発できていないので、
いざやってみるといいですね。
やはりtestemが良い感じです。

次回はフロント側を勉強するためBackbone.jsを扱う予定です。