Tagbangers Blog

JavaScriptのテストフレームワーク - Jasmine編 -

テストを書きましょう。

JavaScriptも例に漏れずテストは重要です。

とは言っても私はJavaScriptのテストなんてほぼ書いたことありません。

そんな私ですが、今回はJavaScriptのテストフレームワークを触ってみたいと思います。

JavaScirptのテストフレームワーク

あまり調べたことがありませんでしたが、JavaScriptにもかなりの数のテストフレームワークが存在するようです。

QUnit

元々はjQueryをテストするためのテストフレームワークだったが、jQueryに依存しない単体テストのフレームワークになった。そうです。

リグレッションテストに向いている。

JsUnit

JUnitのJavaScript移植版。

現在はメンテナンスされてない。

pivotalはこちらにいるようです。→ Jasmine

Selenium

UIテストといえば!JavaScriptのテストももちろん出来るよう。

YUI Test

Yahoo!お手製のテストフレームワーク。

守備範囲が広い。

イベントのシミュレーションが素晴らしい。

riotjs

rubyのユニットテストフレームワークのJavaScript移植版。

最近社内で熱いコチラではありません。

使って見よう

かなり種類があるようですが、今回は「Jasmine」を使ってみようと思います。

タグバンといえばSpringといえばPivotalということで。

まずは公式にありますように一式をダウンロードして、展開しましょう。

release page 

中身はこんな感じです。

[jasmine] tree                                                                  
.
├── MIT.LICENSE
├── SpecRunner.html
├── lib
│   └── jasmine-2.4.1
│       ├── boot.js
│       ├── console.js
│       ├── jasmine-html.js
│       ├── jasmine.css
│       ├── jasmine.js
│       └── jasmine_favicon.png
├── spec
│   ├── PlayerSpec.js
│   └── SpecHelper.js
└── src
    ├── Player.js
    └── Song.js

次に結果サンプルのSpecRunner.htmlを開いてみます。

なかなか綺麗にテスト結果が表示されるようです。シンプルでいいですね!


言葉の整理

少しだけ用語の整理をしておきます。


suite

テストクラス。テスト対象全体と言った感じでしょうか。

Specをまとめる、describeを実行します。

describeはネストすることも可能です。

describe("テスト1", function() {
  ...
  describe("テスト1-1", function() {

  });
})

spec

テストそのもの。it関数で宣言します。

var a = 1;

it("aは1です", function() {
  expect(a).toEqual(1);
});


使ってみる

全体としては以下のような感じになります。

describe("Article", function() {
  var article;

  beforeEach(function() {
    article = new Article();
  });

  it("articleにはbodyがある。", function() {
    article.setBody("body");
    expect(body.getBody()).not.toBe(null);
});


テストの本質は

    expect(body.getBody()).not.toBe(null);

この書き方はRSpecを踏襲しており人が読んで分かりやすい書き方になってます。

JavaのJUnitでもHamcrestなどは似たような書き方なので、Javaな人たちにも親しみ易い書き方ではないかと思います。


Matcher

expectに続くもので、評価基準・評価方法でデフォルトのもの以外にもcustom matcherが簡単に作成出来ます。

Jasmine has a rich set of matchers included.

かなり豊富に揃っているようなので全ては書けませんが、気になるものを少しだけご紹介。

・toBe、toEqual

toBeは " === "で比較するので、型変換なしに厳密に等しくないといけません。

it("1と'1'", function() {
      var a = 1;
      expect(a).toEqual(1);
      expect(a).toBe("1"); // fail!
});

・toContain

配列が指定した要素を含んでいる。

it("Taroを含む", function() {
      var players = ["Taro", "Jiro", "Sub"];
      expect(players).toContain("Taro");
});

・toBeGreaterThan、toBeLessThan

より大きい、より小さい。以上、以下では無いので注意。

it("aはbよりでかい", function() {
      var a = 100;
      var b = 1;
      expect(a).toBeGreaterThan(b);
});

・toThrow、toThrowError

どちらもexceptionの発生チェックだが、toThrowErrorはエラーの種類、メッセージを指定できる。

it("TypeErrorが投げられる", function() {
    var foo = function() {
      throw new TypeError("hoge fuga");
    };

    expect(foo).toThrowError("hoge fuga");
    expect(foo).toThrowError(/hoge/);
    expect(foo).toThrowError(TypeError);
    expect(foo).toThrowError(TypeError, "hoge fuga");
  });

Setup and Teardown

・beforeEach、afterEach

それぞれのテストケースが実行される前(もしくは後)に必ず実行される。

毎回実行されるので、負荷の高い処理は書かない方がよい。(書いてはいけない)

・beforeAll、afterAll

テストを実行したとき(実行後)に1回だけ実行される。

負荷の高い処理を実行したい場合は有効。

ただし、テストケース毎に変数がリセットされるわけではないので、使用するときは注意が必要。


this

beforeEach→ it → afterEach 間で共有。

もちろんことなる spec 間ではリセットされる。

describe("A spec", function() {
  beforeEach(function() {
    this.foo = "beforeEachで初期化";
  });

  it("thisは引き継がれる", function() {
    expect(this.foo).toEqual("beforeEachで初期化");
    this.bar = "barは引き継がれる?";
  });

  it("次のspecに行くときはからのthisが作られる!", function() {
    expect(this.foo).toEqual("beforeEachで初期化");
    expect(this.bar).toBe(undefined);
  });
});


Pending

xdescribe、xitで実行しないsuite,specを書くことが出来る。

またpending関数をspec中で実行することでそのspec毎pendingできる。

suite終了後に理由としてpendingに渡したメッセージが表示される。

it("Pending spec", function() {
    expect(true).toBe(false);
    pending('this is why it is pending');
});


Spy

メソッドを乗っ取る事ができます。

自由に振舞を変えたり、メソッド本来の処理を実行したり自由自在です!

他のクラスに依存している処理がある場合など、テスト対象とは異なる処理を乗っ取りましょう。


時を操る

setInterval、setTimeoutなど、時間が経過したときに実行されるメッソドは通常のやり方ではテストできません。

例えば。

var a = 0;
setTimeout(function() {
    a = 100;
}, 1000);

expect(a).toBe(100); // fail!

この場合setTimeoutの処理はaの評価を行う時点では実行されないため、期待する状態にはなりません。

でも、Jasmineなら大丈夫ですよ!

var a = 0;
setTimeout(function() {
    a = 100;
}, 1000);
jasmine.clock.tick(1001)
expect(a).toBe(100); 

これで時間を自在に操ることが出来ます。

素敵ですね。


Jasmineはとてもシンプルで使いやすいフレームワークだと思います。

今回は触れませんでしたが、非同期処理などもテスト出来そうです。


ただし、UIを伴うテストは範疇では無いと思いますので、また違うツールを使って統合的なテストを行える環境を作っていきたいと思います!


riot.jsのテストも書かなければ。。。

(どうやらJasmine使えるようですぞRiotのテストと宿命の反乱(karma-riot) )

では、よいテストライフを。

関連Link