JavaScript BDD/Testing / Jasmine 2.0 & Dark Theme

JavaScriptの Testing/BDD Frameworkを Jasmineに変えたので、Jasmine 2.0について自分なりにまとめてみた。

入手

Homepage: http://jasmine.github.io/

Download: https://github.com/pivotal/jasmine/tree/master/dist

使い方

2.0をダウンロードして解凍する。

jasmine.js, jasmine-html.js, boot.js, jasmine.cssは 適宜test用js/css置き場を作って置く。

作成中のWeb Applicationのindex.htmlと同じ場所あたりに SpecRunner.htmlを test.html等として置く。先ほどのjasmine用cssとjsのパスは適宜あわせる。

test.htmlを編集してテスト対象のsourceファイルとspecファイルの読み込みを入れる。

browserからtest.htmlを開くとテストが実行される。

各項目をクリックするとテスト範囲を絞って実行する事が出来る。特定項目の試験をBookmarkしておくと便利。

Specの書き方

基本はdescribe(‘これは’, function() { it(‘こうあるべき’, function () { expect(実際の値).toBe(期待する値) }); }); といった形で書くだけ。

describe('hogeとhehe', function () {
    it('hogeはheheであってはならない。', function () {
        expect('hoge').not.toBe('hehe');
    });
});

toBeのところ、Matcherは自分で書く事もできるし、最初からいくつか準備されている。

describeは入れ子にもできる

describe('hogeとhehe', function () {
    it('hogeはheheであってはならない。', function () {
        expect('hoge').not.toBe('hehe');
    });

    describe('hoge', function () {
        it('hogeはhogeである', function () {
            expect('hoge').toBe('hoge');
        });
    });
});

ネストはテストの事前準備と後処理を書いた場合に結構重要。

specのjsファイルにエラーがあってロードできない場合、以下の様にテスト正常終了のような画面になるが安心してはいけない。

事前準備と後処理

各テストの前に実行される 事前準備と、各テストが終わったあとに実行される後処理を書く事が出来る。

beforeEachとafterEachがそれ。各it の前後で実行されます。

ソースコード

function Foo() {
    this.hoge = 'hoge';
    this.hehe = 'hehe';
}

Foo.prototype.transform = function () {
    this.hehe = 'hoge';
};

Foo.prototype.dispose = function () {
    delete this.hoge;
    delete this.hehe;
};

テストコード

describe('クラス Foo', function () {
    var foo;

<strong>    beforeEach(function () {
        foo = new Foo();
    });

    afterEach(function () {
        foo.dispose();
        foo = null;
    });</strong>

    describe('プロパティ', function () {
        describe('hoge', function () {
            it('hogeはhogeである', function () {
                expect(foo.hoge).toBe('hoge');
            });
        });

        describe('hehe', function () {
            it('heheはheheである', function () {
                expect(foo.hehe).toBe('hehe');
            });
        });

        describe('変身したhehe', function () {
            <strong>beforeEach(function () {
                foo.transform();
            });</strong>

            /* ここの itは同じスコープのbeforeEachだけでなく、外側のスコープにあるbeforeEach/afterEachが実行されます。 */
            it('heheは変身するとhogeである。', function () {
                expect(foo.hehe).toBe('hoge');
            });
        });
    });
});

当たり前ですが、テストはit 一つだけとか、describeの単位で実行できるようにしておくべきです。でないと毎回全部実行する事になってしまう。

そのためにはテストの事前準備と後処理をしっかりやらないといけない。あるテストの後じゃないと動かないってのはダメ。

上のコードだと、beforeEachとafterEachはそのスコープにあるitが実行されるときの前後で必ず実行される事になりますので、入れ子にしたdescribeの itの前後でも実行されます。

Core Matcher

jasmineで最初から使えるMatcherは以下

expect(x).<strong>toEqual(y)</strong>; compares objects or primitives x and y and passes if they are equivalent
expect(x).<strong>toBe(y)</strong>; compares objects or primitives x and y and passes if they are the same object
expect(x).<strong>toMatch(pattern)</strong>; compares x to string or regular expression pattern and passes if they match
expect(x).<strong>toBeDefined()</strong>; passes if x is not undefined
expect(x).<strong>toBeUndefined()</strong>; passes if x is undefined
expect(x).<strong>toBeNull()</strong>; passes if x is null
expect(x).<strong>toBeTruthy()</strong>; passes if x evaluates to true
expect(x).<strong>toBeFalsy()</strong>; passes if x evaluates to false
expect(x).<strong>toContain(y)</strong>; passes if array or string x contains y
expect(x).<strong>toBeLessThan(y)</strong>; passes if x is less than y
expect(x).<strong>toBeGreaterThan(y)</strong>; passes if x is greater than y
expect(function(){fn();}).<strong>toThrow(e)</strong>; passes if function fn throws exception e when executed

あまり書く事もないかな。

toBeは単純に===比較なのに対して toEqualはオブジェクトの各プロパティを比較する。

以下のexpectは同じプロパティを持っていても、別オブジェクトなので toBeだと失敗する。

toEqualだと成功する。

expect(
    {name: 'foo', child: {name:'hoge'}}
).toEqual(
    {name: 'foo', child: {name:'hoge'}}
);

toEqualでは 深く潜って比較するので以下のexpectは失敗。

expect(
    {name: 'foo', child: {name:'<strong>hoge</strong>'}}
).toEqual(
    {name: 'foo', child: {name:'<strong>hehe</strong>'}}
);

否定したい場合はnotを入れる。

expect(x).<strong>not</strong>.toBe(y)

Custom Matcher

独自のMatcherを入れたい場合はbeforeEachかitでaddMachersを呼び出す。

beforeEach(function() {
  this.addMatchers({
    toBeFooBar: function(expected) {
      return fooBarCompare(this.actual, expected);
    }
  });
});

無名関数でthisを使ってるのが慣れない(^^;

非同期テスト

とっても簡単。beforeEachと itで渡す関数に対して引数doneを追加する。

以下は非同期でのポーリング例

beforeEach(function(<strong>done</strong>) {

    function areYouReady() {
        if (you.areReady())
            <strong>done();</strong>
        else
            setTimeout(areYouReady, 100);
    }

    areYouReady();
});

doneが呼ばれるまで準備完了とならず、先に進みません。

it('should be fine', function(<strong>done</strong>) {
    getYourCondition(function (condition) {
        expect(condition).toBe('fine');
        done();
    });
});

こちらも同じ。doneが呼ばれるまでテスト完了にならず、先に進みません。

最後に。おまけ:Dark Theme

という感じでJasmine 2.0。とってもSimpleに使えるのが良いなと思います。

ただ、デフォルトで背景が白で明るいのと文字が小さすぎるので、自分好みに変えたテーマを用意してみました。

cssをjasmine.cssといれかえて使ってみてください。

Download: dark_jasmine.css