モンモンブログ

技術的な話など

5分で分かる jasmine テストフレームワーク

JavaScript のテストフレームワーク jasmine がゴキゲンだぜ。書きやすいしテスト結果も見やすいしかわいいよちゅっちゅ。

そんな jasmine ちゃんをまだ触ったことない人のために、出来るだけ取っ付き易くなるように解説してみたいと思います。拙作の jQuery プラグイン jquery.narrows.js のテストでも使っておりますので、これをサンプルとして説明していきます。

使用するバージョンは jasmine 2.0.0 RC5 スタンドアローン版。2系はまだRC版ですが、使っていて特に不具合に出くわしたことはないです。2系になって大きく改善してる部分もあるので、アグレッシブに使っていきましょう。(Rubyフレームワーク用の gem 版(jasmine-gem)というのもありますがここでは触れません)

まずは見てみよう

とりあえず実際に動くものを見てみましょう。jquery.narrows.js のサンプルページ兼テストページがこちら。 → sample.html

画面の一番下までスクロールすると、こんな↓感じに jasmine のテスト結果が表示されてるのが見えるかと思います。

f:id:ymdsmn:20131210065327p:plain

  • food は初期状態で disabled であるべき
  • category で "meat" を選択したら food は肉に絞り込まれるべき
  • category で value="" を選択したら food は disabled であるべき
  • などなど…

といったテストを実行して、全て成功しています。

ちなみにテストに失敗した場合の表示はこんな感じ。

f:id:ymdsmn:20131210074642p:plain

jasmine 導入方法

導入の仕方について。

公式のダウンロードページには現在バージョン1系しか置いてなくて、2系は master ブランチの "dist" ディレクトリからダウンロードできます。 現時点での最新版は jasmine-standalone-2.0.0-rc5.zip です。

ダウンロードして解凍すると、ファイル構成はこんな感じ。

├── MIT.LICENSE
├── SpecRunner.html
├── lib
│   └── jasmine-2.0.0-rc5
│       ├── boot.js                 *
│       ├── console.js
│       ├── jasmine-html.js         *
│       ├── jasmine.js              *
│       ├── jasmine.css             *
│       └── jasmine_favicon.png
├── spec
│   ├── PlayerSpec.js
│   └── SpecHelper.js
└── src
    ├── Player.js
    └── Song.js

このうち、ブラウザ上でのテスト実行に必要なのは を付けたものだけです。これを自分のプロジェクト以下に置いて、HTML に読み込ませます。

<link rel="stylesheet" href="lib/jasmine-2.0.0-rc5/jasmine.css">
<script src="lib/jasmine-2.0.0-rc5/jasmine.js"></script>
<script src="lib/jasmine-2.0.0-rc5/jasmine-html.js"></script>
<script src="lib/jasmine-2.0.0-rc5/boot.js"></script>
<script src="spec/narrowsSpec.js"></script>   <!-- テストファイル -->

js ファイルは読み込む順番が決まっており、上に書いたとおり

  • jasmine.js
  • jasmine-html.js
  • boot.js
  • テストファイル(ここでは narrowsSpec.js

の順にして下さい。

これだけで、さっき見たようなテスト結果が画面下に表示されるようになります。

テストの書き方

じゃあ実際のテストはどんな風に書くのか?

サンプルで実行しているテストファイルはこちら。 → narrowsSpec.js

jasmine は RubyRSpec などの記法を踏襲しているので、RSpec などを使ったことのある人には馴染みやすいと思います。

抜粋して解説します。

describe('例1', function() {
    // select 要素を jQuery オブジェクトとして取得しておく
    var $categorySelect = $('#ex1-food-category');
    var $foodSelect = $('#ex1-food');

    // 各テストの最初に全ての select の選択状態を初期化
    beforeEach(function() {
        $categorySelect.val('').trigger('change');
        $foodSelect.val('').trigger('change');
    });

    it('food select は初期状態で disabled', function () {
        expect($categorySelect.is(':disabled')).toBe(false);
        expect($foodSelect.is(':disabled')).toBe(true);
    });

    it('category select で "meat" を選択したら food select は肉に絞り込まれる', function () {
        // category select で "meat" を選択
        $categorySelect.val('meat').trigger('change');
        // food select の disabled が解除されたことを確認
        expect($categorySelect.is(':disabled')).toBe(false);
        expect($foodSelect.is(':disabled')).toBe(false);
        // food select が絞り込まれた結果、value="" でない option が最低1つ以上あることを確認
        expect($foodSelect.find('option[value!=""]').size()).toBeGreaterThan(0);
        // food select の option の data-ex1-food-category 属性が全て "meat" であることを確認
        $foodSelect.find('option[value!=""]').each(function () {
            expect($(this).data('ex1-food-category')).toBe('meat');
        });
    });
});

describe は、複数のテストをまとめる働きをします。

describe('例1', function() {
    //...
});

階層構造にすることも出来ます。

describe('例1', function() {
    describe('例1-1', function() {
        //...
    });
    //...
});

この describe の中に、テスト本体である it や、前処理の beforeEach、後処理の afterEach を書いていきます。

beforeEach は名前の通り、各テスト毎に実行させたい前処理を書きます。

    beforeEach(function() {
        // 前処理
    });

afterEach は各テスト毎の後処理。

    afterEach(function() {
        // 後処理
    });

it がテスト本体です。テストの中身を記述します。

    it('food select は初期状態で disabled', function () {
        expect($categorySelect.is(':disabled')).toBe(false);
        expect($foodSelect.is(':disabled')).toBe(true);
    });

it 中にいっぱい出てくる expect がキモです。

        expect($categorySelect.is(':disabled')).toBe(false);

この場合、 $categorySelect.is(':disabled') の結果が false であることを期待する(expect)、という意味。

it 中の expect が1つでも失敗すれば、そのテストは失敗扱いとなります。

expect にチェーンしているメソッド toBeMatcher といいまして、expect に渡した中身が条件にマッチするかを判定します。

様々なバリエーションがあります。

// foo === 1 であるか
expect(foo).toBe(1);

// foo == 1 であるか
expect(foo).toEqual(1);

// 文字列が正規表現にマッチするか
expect(foo).toMatch(/^[a-z]+$/);

// 変数が定義済みか
expect(foo.func).toBeDefined();

// 変数が未定義か
expect(foo.func).toBeUndefined();

// 変数がnullか
expect(foo).toBeNull();

// 変数が true 相当の値か(true, 1, "a" など)
expect(foo).toBeTruthy();

// 変数が false 相当の値か(false, 0, 空文字列など)
expect(foo).toBeFalsy();

// 配列が値を含んでいるか
expect([1, 2, 3]).toContain(3);

// foo < 2 であるか
expect(foo).toBeLessThan(2);

// foo > 0 であるか
expect(foo).toBeGreaterThan(0);

// 小数が有効数字 0 ケタで 3 に等しいか
expect(3.141592).toBeCloseTo(3, 0);

// 関数 func が何らかの例外を投げることを期待
expect(func).toThrow();

とりあえず今回はここまで。