読者です 読者をやめる 読者になる 読者になる

QUnit はオワコン!?Jasmine を使ってみる

javascript

はじめに

JavaScript でテストするためのフレームワークQUnit と Jasmine が人気を二分していたみたいですが、最近は Jasmine が優勢?雑誌やブログで Jasmine を推しているのをよく見かけました。中には「QUnitオワコン。これからは Jasmine!」って感じの過激な意見も。
私は QUnit を使ってきましたが、Jasmine の勢いは見逃せない。
f:id:griefworker:20120313200718j:image
ってわけで Jasmine 試してみます。どちらが優れているかは、実際に使ってみないと分からないですから。

Jasmineについて

RSpec 風に記述できる、JavaScript のテスティングフレームワークスタンドアロン版とRuby版とNode版があります。スタンドアロン版は QUnit 同様に、ブラウザで実行するタイプ。Ruby 版と Node 版は、なんとコマンドラインから実行できます。
コマンドラインから実行できるということは…Jenkins のジョブに組み込めるかも!?オラ、ワクワクしてきたぞ。

スタンドアロン版を試してみる

スタンドアロン版の入手

下記のページで、スタンドアロン版 Jasmine 一式をダウンロードできます。

ダウンロードしたファイルは適当なディレクトリに解凍します。中身はこんな感じ。

C:.
└─lib
    └─jasmine-1.1.0
            jasmine-html.js
            jasmine.css
            jasmine.js
            jasmine_favicon.png
            MIT.LICENSE
Product.js を作成

テストを書くには、対象のクラスが必要なわけで。

var Product = function(name, price) {
    if (!name) {
        throw "name is required!";
    }
    if (!price) {
        throw "price is required!";
    }
    this.name = name;
    this.price = price;
};
Product.prototype.calcTax = function() {
    return this.price * 0.05;
};
Product.prototype.getPriceWithTax = function() {
    return this.price + this.calcTax();
};

このクラスのテストを書きます。

テストを記述

spec ディレクトリを作って、その下に ProductSpec.js を作ります。ProductSpec.js の内容は次の通り。

describe("Product クラスは", function() {
    var product = null;

    // it の前に実行される
    beforeEach(function() {
        product = new Product("foo", 100);
    });

    // it の後に実行される
    afterEach(function() {
        product = null;
    });

    it("price を null にするとエラーが発生する", function() {
        expect(function() {
            new Product("foo", null);
        }).toThrow("price is required!");
    });

    it("name プロパティで名前を変更できる", function() {
        product.name = "bar";
        expect(product.name).toEqual("bar");
    });

    it("price プロパティで価格を変更できる", function() {
        product.price = 200;
        expect(product.price).toEqual(200);
    });

    it("calcTax メソッドで消費税を取得できる", function() {
        expect(product.calcTax()).toEqual(5);
    });

    it("getPriceWithTax メソッドで税込の価格を取得できる", function() {
        expect(product.getPriceWithTax()).toEqual(105);
    });

    it("name を null にするとエラーが発生する", function() {
        expect(function() {
            new Product(null, 100);
        }).toThrow("name is required!");
    });
});

Jasmine を使って書いたテストは、確かに RSpec で書いたテストによく似てますね。

また、describe は入れ子にできるから、状態でグループ分けできます。Backbone.js や Knockout.js で作成したアプリと相性良さそう。実際 Knocout アプリのテストが書きやすかったです。経験者談。

SpecRunner を作成

テストを実行するための HTML ファイルが必要なんですが、スタンドアロン版の ZIP には入っていませんでした。仕方ないので、ネットの情報を参考に SpecRunner.html を作成します。

<!DOCTYPE html>
<html>
    <head>
        <title>Jasmine Test Runner</title>
        <meta charset="utf-8"/>
        <link rel="stylesheet" type="text/css" href="lib/jasmine-1.1.0/jasmine.css">
        <script type="text/javascript" src="lib/jasmine-1.1.0/jasmine.js"></script>
        <script type="text/javascript" src="lib/jasmine-1.1.0/jasmine-html.js"></script>

        <!-- include spec files here... -->
        <script type="text/javascript" src="Product.js"></script>
        <script type="text/javascript" src="spec/ProductSpec.js"></script>
    </head>
    <body>
       <script type="text/javascript">
            jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
            jasmine.getEnv().execute();
        </script>
    </body>
</html>

多分これでいいはず。SpecRunner.html のスケルトンをスタンドアロン版に同梱してくれればいいのに。入れ忘れだったり?

SpecRunner.html をブラウザで表示

この時点で、ディレクトリ構成はこんな感じになりました。

C:.
│  Product.js
│  SpecRunner.html
│
├─lib
│  └─jasmine-1.1.0
│          jasmine-html.js
│          jasmine.css
│          jasmine.js
│          jasmine_favicon.png
│          MIT.LICENSE
│
└─spec
        ProductSpec.js

SpecRunner.html をブラウザで開くと
f:id:griefworker:20120313195037p:image
テストの実行結果が出力されています。すべて成功してますね。describe や it に渡す文字列を工夫したので、クラスの振る舞いが分かるようになっています。スバラシイ。

RubyGem 版も試してみる

Jasmin の RubyGem をインストール

Jasmin は RubyGem が公開されているので、gem install コマンドでインストールできます。でも、私は RubyGem をプロジェクト内にダウンロードしたい派なので、Bundler を使います。

まず Gemfile を作成。

source :rubygems

gem 'jasmine'
gem 'rake'

そして、Jasmine の RubyGem をインストール。

bundle install ./vendor/bundle
テスト用ファイル生成
bundle exec jasmin init

を実行すると次のような、Jasmine を使ってテストを実行するために必要なファイルと、テストのサンプルが生成されます。

C:.
│  Gemfile
│  Gemfile.lock
│  Rakefile
│
├─.bundle
│      config
│
├─public
│  └─javascripts
│          Player.js
│          Song.js
│
├─spec
│  └─javascripts
│      │  PlayerSpec.js
│      │
│      ├─helpers
│      │      SpecHelper.js
│      │
│      └─support
│              jasmine.yml
│              jasmine_config.rb
│              jasmine_runner.rb
│
└─vendor
    └─bundle
        └─ここから下は gem のディレクトリなので省略

jasmine.yml で、テストを実行するときに読み込むソースファイルを指定しています。

# src_files
#
# Return an array of filepaths relative to src_dir to include before jasmine specs.
# Default: []
#
# EXAMPLE:
#
# src_files:
#   - lib/source1.js
#   - lib/source2.js
#   - dist/**/*.js
#
src_files:
    - public/javascripts/**/*.js

# stylesheets
#
# Return an array of stylesheet filepaths relative to src_dir to include before jasmine specs.
# Default: []
#
# EXAMPLE:
#
# stylesheets:
#   - css/style.css
#   - stylesheets/*.css
#
stylesheets:

# helpers
#
# Return an array of filepaths relative to spec_dir to include before jasmine specs.
# Default: ["helpers/**/*.js"]
#
# EXAMPLE:
#
# helpers:
#   - helpers/**/*.js
#
helpers:

# spec_files
#
#  Return an array of filepaths relative to spec_dir to include.
# Default: ["**/*[sS]pec.js"]
#
# EXAMPLE:
#
# spec_files:
#   - **/*[sS]pec.js
#
spec_files:

# src_dir
#
# Source directory path. Your src_files must be returned relative to this path. Will use root if left blank.
# Default: project root
#
# EXAMPLE:
#
# src_dir: public
#
src_dir:

# spec_dir
#
# Spec directory path. Your spec_files must be returned relative to this path.
# Default: spec/javascripts
#
# EXAMPLE:
#
# spec_dir: spec/javascripts
#
spec_dir:

どうやら、テスト対象のクラスは public/javascript に配置し、テストは spec/javascript に配置すればいいみたいです。

テストを実行

スタンドアロン版を試したときに作成したテストを流用します。モッタイナイ。Product.js を public/javascript に移動し、ProductSpec.js を spec/javascript に移動します。あと、jasmin init で生成されたテストのサンプルは削除。

以上を行ったディレクトリ構成はこんな感じ。

C:.
│  Gemfile
│  Gemfile.lock
│  Rakefile
│
├─.bundle
│      config
│
├─public
│  └─javascripts
│          Product.js
│
├─spec
│  └─javascripts
│      │  ProductSpec.js
│      │
│      ├─helpers
│      │      SpecHelper.js
│      │
│      └─support
│              jasmine.yml
│              jasmine_config.rb
│              jasmine_runner.rb
│
└─vendor
    └─bundle
        └─ここから下は gem のディレクトリなので省略

あとは

bundle exec rake jasmin:ci

でテストを実行します。すると
f:id:griefworker:20120313195038p:image
という風にブラウザが自動で立ち上がって、テストが実行されて、ブラウザが閉じます。おおお、ブラウザが自動で動いたよ。そういえば Selenium 初めて使ったかも。

まとめ

HTML + JavaScript で動くのは QUnit と同じですが、Jasmine ではテストを RSpec 風に書ける上に、 Ruby 版を使えばコマンドラインから実行できます。

QUnit と同じどころか、それ以上のことができる。正直、私は Jasmine を選ばない理由が見つからなかったです。