今年の RubyKaigi 2010で、 Sarah Meiさんによる JasmineというJavaScriptのテスティングフレームワークの紹介発表がありました(発表時はRCでしたが、先日(2010年9月14日)に無事バージョン1.0がリリースされています)。

今回はこの Jasmine を紹介したいと思います。

特徴

Jasmineは以下のような特徴を持ったテスティングフレームワークです。

  • JavaScript自身でJavaScriptをテスト可能
  • RSpec風の記述が行える
  • スタンドアローン版とRubyGem版

JavaScript自身でJavaScriptをテスト可能

テスト用に専用の言語・フォーマットを使う必要はありません。

RSpec風の記述が行える

全体の構造は、RSpecのユーザーにはお馴染のdescribe や it という記法で記述します。 Rubyのブロックほど簡潔には書けませんが、Functionオブジェクトを使ってそれらしく書くことが出来ます。

describe("sprintf", function() {
  it("should return a string on success", function() {
  });
});

スタンドアローン版とRubyGem版

スタンドアローン版は

  • jasmine本体
  • テスト実行用のHTML(とその構成ファイル)
  • サンプルプログラムとテスト

RubyGem版は加えて

  • Rails2プロジェクト用ジェネレータ
  • Rails2プロジェクト以外への組み込み時に使用するコマンドラインツール(Rails3でも使用)
  • Rakeタスク
  • Selenium連携

が含まれます。

実行

スタンドアローン版の場合

スタンドアローン版は付属するSpecRunner.html をブラウザで開くと実行できます。

付属のサンプルを実行した例

SpecRunner.htmlをブラウザで開く

通常は結果のサマリー部分(5 specs, 0 failuresのところ)のみが表示されますが、 上部の Show passed にチェックを入れてあるので、パスしたテストの内容も表示されています。

対象となる実装コード

<script type="text/javascript" src="src/sprintf.js"></script>

およびspecコード

<script type="text/javascript" src="spec/sprintf_spec.js"></script>

は、SpecRunner.html 内に直接追加します。

なお、日本語の出力をしたい場合は、meta要素によるcharset指定を使いましょう。

gem版の場合

準備

若干準備が必要です。

  • script/generate jasmine (Rails 2の場合)
  • bundle exec jasmine init (Rails 3の場合)
  • jasmine init (その他の場合)

いずれの場合でも

  • サンプル(実装とspec)
  • Jasmine用設定ファイル(spec/javascripts/support/*)
  • Rakeタスク(lib/tasks/jasmine.rake)

などが作られます。

テスト対象ファイルは spec/javascripts/support/jasmine.yml に書かれた

src_files:
  - public/javascripts/**/*.js
spec_files:
  - **/*[sS]pec.js

のようなファイル名パターンに従って自動的に集められます。

サーバ経由での実行

Rakeタスク

rake jasmine

を実行すると、ポート8888でテストサーバが起動します。 この状態で http://localhost:8888/ にアクセスすると、スタンドアローン版と同様のテストページを表示できます。

CIでの実行

CI環境からRakeタスク jasmine:ci を実行します。ブラウザが開き、Selenium RCでテストが行われます。

RakeからSelenium RCを使って実行

Jasmineの構成要素

describe, it

前述の通り、describe と it が基本構造になります。

describeで作られる構造を Suite、it で作られる構造を Spec と呼びます(RSpecではそれぞれ ExampleGroup(Context) と Example)。

expect

RSpecの 式.should の代わりに expect(式) を使います。

マッチャー

expect(式) に対してマッチャーを呼び出してチェックを行います。

expect("Name: %s Age: %d".sprintf("John Doe", 42).toEqual("Name: John Doe, Age: 42");

マッチャーの例

  • .toEqual 同値かどうか。
  • .toBe 同じオブジェクトかどうか。
  • .toMatch 正規表現に一致するか。
  • .toBeDefined undefinedでないか。
  • .toBeNull nullか。
  • .toBeTruthy / .toBeFalsy true / false か。
  • .toContain 配列や文字列に含むか。
  • .toBeLessThan / .toBeGreaterThan 大小比較。
  • .toThrow 例外を発生させるか。
  • .not expectとマッチャーの間に書き、条件を否定にします。

あらたにマッチャーを追加することも可能です。

スパイ

オブジェクトのメソッドの呼び出しをスパイを使って監視することが出来ます。

spyOn(x, 'method')

とすると、x.method (Functionオブジェクト) に成り代わるスパイが作られます。

スパイが呼び出されたときの行動に関しては、いくつかのバリエーションがあります。

  • spyOn(x, 'method').andReturn(arguments) 呼び出しに対して arguments を返す。
  • spyOn(x, 'method').andThrow(exception) 呼び出しに対して例外を発生させる。
  • spyOn(x, 'method').andCallFake(function) 呼び出しに対して、代わりに function を呼び出す。
  • spyOn(x, 'method').andCallThrough() x.method(...) そのまま本来のメソッド呼び出しを行わせる。

テストコードを評価した後、スパイに対して

expect(x.method)

として、専用のマッチャーを使って結果を報告させることが出来ます。

  • .toHaveBeenCalled() 呼ばれたか。
  • .toHaveBeenCalledWith(arguments) 引数 arguments を伴って呼ばれたか。
  • 通常の expect 時と同じく、 .not で否定することも出来ます。

スパイはされたメソッドは以下のプロパティを持ちます。

  • .callCount 呼び出し回数
  • .mostRecentCall.args 直近の呼び出し時の引数
  • .argsForCall[i] i番目の呼び出し時の引数

非同期

runsとwaitsForを使って非同期呼び出しを制御することが出来ます。 AJAXコードの挙動を調べるために活用できます。

runs(function)

functionを実行します。

複数のrunsは順番に実行されます。

waitsFor(function, message, timeout)

functionがtrueを返す、またはtimeout(単位: ミリ秒)の時間経過を待ちます。(標準では10msec間隔) trueを返さずにtimeout経過してしまった場合はSpecの実行を中止し、messageを返します。

イメージしづらいので、スタンドアローン版でサンプルコードを書いてみました。

以下のファイル src/demo.js はTwitterのPublic Timelineを取得するものです。jQueryを使用しています。 loadメソッドがTwitterのPublic Timeline APIを非同期で呼び出し、resultメソッドで結果の配列を取得することが出来ます。取得できるまではresultは長さゼロの配列。エラー処理はしていません。

function PublicTL() {
  var url = 'http://api.twitter.com/1/statuses/public_timeline.json?callback=?';
  var tl = [];
  return {
    result: function result() {
      return tl;
    },
    load: function load() {
      tl = [];
      $.getJSON(url, {}, function(json) {
        tl = json;
      });
    },
  };
};

Spec は、 spec/demo_spec.js に以下のように1つだけ書きました。Public Timelineは20件のデータが取れるはずなので、それをテストしています。

describe('PublicTL', function() {
  var tl = new PublicTL();
  it('should get the public TL', function() {
    tl.load();
    waitsFor(function() {
      return tl.result().length > 0;
    }, 'timeout', 1000);
    runs(function() {
      expect(tl.result().length).toEqual(20);
    });
  });
});

SpecRunner.html に以下のようなscript要素を加えてブラウザで開きます。

Google Library APIを使用して jQuery をロード

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">google.load("jquery", "1.4.2");</script>

実装ファイル

<script type="text/javascript" src="src/demo.js"></script>

Spec

<script type="text/javascript" src="spec/demo_spec.js"></script>

その他

beforeEach, afterEach

各Spec(it) ごとに毎回評価される関数を宣言します。RSpec の before(:each) / after(:each) に相当します。

RSpecの before(:all) / after(:all)に相当するものはありません。

Suiteのネスト

Suite(describe) はネストさせることができます。

スキップ

describe / it それぞれを xdescribe / xit と書き換えることで、 その配下の Suite / Spec の実行をスキップすることが出来ます。 RSpec の pending に類似した機能です。

まとめ

  • Jasmineは、RSpecに似通った記法を使ってJavaScriptのテスティングを行うフレームワークです。
  • マッチャーにより、さまざまな条件をテストすることが出来ます。カスタマイズも可能です。
  • 非同期な動作もテストすることが出来ます。
  • CIに統合することが可能です。
  • このエントリーをはてなブックマークに追加
エンジニア募集中です!