Jasmineの使い方で学ぶJavaScriptのテストコード入門

テスト駆動開発とは

プログラムの開発手法のひとつに「テスト駆動開発」というものがあります。

テスト駆動開発とは、先に動作結果を満たしているかを確認するためのコード(テストコード)を書いておき、テストコードでエラーが出なくなるように設計してプログラミングしていく手法のことです。

テストコードを書くのはなぜ?

元来、テストというのは「試験」「確認」の意味合いが強いため、一通りのプログラミングが完了した後に品質チェックとして行われていました。

しかし、大規模で高度に複雑化したシステムに改修や変更を加える場合、一部に手を加えただけで影響が広範囲に及んでしまう可能性もあります。

これまでのように、プログラミングが完了した後に一つ一つを人間が確認していたのでは、どこに影響が及んでいるかを見つけ出すために膨大な時間が必要なため、柔軟な変更が困難になります。

そこで、先にテストコードを書いておけば変更箇所以外に影響が及ぶのを確認でき、開発しながら確認することで開発スピードを格段に上昇させることができるようになるわけです。

テスト駆動開発の流れ

最も基本的な開発サイクルは次の通りです。

  1. 失敗するテストを書く
  2. テストがパスするような最小限のコード本体を書く
  3. コードの重複を除去する(リファクタリング)

失敗するテストを書く

まず最初にエラーがきちんと出るかを確認します。

この段階でエラーを出さずにパスしてしまった場合、テストコードに問題がある可能性があります。
エラーが出るようになるまではプログラムコード本体を触ってはいけません。

テストがパスするような最小限のコード本体を書く

どんな手段を用いてもかまわないので、ひとまずテストがパスするようにプログラミングします。
本来変数を使用する部分に定数を使用しても構いませんし、コピー&ペーストによるソースコードの重複等は、この段階では気にしません。

コードの重複を除去する(リファクタリング)

テストがパスする状態を保持しつつ、コードを再構築していきます。

重複している部分を関数化したり、引数を繋ぎ込んだりして確認します。

JavaScriptでのテストコード

テストコードは基本的にテストフレームワークを利用します。

JavaScript用テストフレームワークの種類

参照元:JavaScriptテストの基礎知識と使えるフレームワーク6選

JavaScriptでのテストコードの書き方(Jasmineの使い方と入門)

本記事ではJasmineを使用してテストコードの書き方について入門していきます。

Jasmine入門

まずはJasmine2.3.4をダウンロードします。

jasmine-standalone-2.3.4をダウンロードして展開し、SpecRunner.htmlをテキストエディタで開いてみてください。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Jasmine Spec Runner v2.3.4</title>
    
    <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.3.4/jasmine_favicon.png">
    <link rel="stylesheet" href="lib/jasmine-2.3.4/jasmine.css">
    
    <script src="lib/jasmine-2.3.4/jasmine.js"></script>
    <script src="lib/jasmine-2.3.4/jasmine-html.js"></script>
    <script src="lib/jasmine-2.3.4/boot.js"></script>
    
    <!-- include source files here... -->
    <script src="src/Player.js"></script>
    <script src="src/Song.js"></script>
    
    <!-- include spec files here... -->
    <script src="spec/SpecHelper.js"></script>
    <script src="spec/PlayerSpec.js"></script>

</head>

<body>
</body>
</html>

これを、このように書き換えて使用します。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Jasmine Spec Runner v2.3.4</title>

    <link rel="shortcut icon" type="image/png" href="lib/jasmine-2.3.4/jasmine_favicon.png">
    <link rel="stylesheet" href="lib/jasmine-2.3.4/jasmine.css">

    <script src="lib/jasmine-2.3.4/jasmine.js"></script>
    <script src="lib/jasmine-2.3.4/jasmine-html.js"></script>
    <script src="lib/jasmine-2.3.4/boot.js"></script>

    <script src="テストしたいJavaScriptのファイルパス"></script>

    <script>
        // ここにテストコードを記述していきます。
    </script>
</head>

<body>
</body>
</html>

テストを実行するためには、このSpecRunner.htmlをブラウザで開きます。

describe()

テストコードでは、describe()で見出しをつけて記述していきます。

<script>
    describe('テストの見出し', function () {
        // テストコード
    });
</script>

describe()は入れ子にすることもできます。

<script>
    describe('テストの見出し', function () {
        describe('テストの見出しa', function () {
            // テストコード
        });
        describe('テストの見出しb', function () {
            // テストコード
        });
    });
</script>

it()

it()でテストに名前をつけます。

<script>
    describe('テストの見出し', function () {
        it('テストの名前', function () {
            // テストコード
        });
    });
</script>

expectメソッドとMatcherメソッド

it()の中に実際にテストコードを書いていくわけですが、ここで基本的な書式を確認しておきましょう。 [js] expect(value).toEqual(5); [/js]

これが基本的なJasmineのテストコードの書き方です。

expect()メソッドの引数に指定した値を、toEqual()などのMatcherメソッドで評価します。

次の項目からMatcherメソッドの種類を紹介していきます。

値が等しいかを確認するテストコード - toEqual()

expect()で指定した値がtoEqual()で指定した値と等しいことを期待します。

<script>
    describe('入門用テストコード', function () {
        var a;
        
        it('値が等しいか', function () {
            a = 1+1;
            expect(a).toEqual(2);
        });
    });
</script>

この場合、変数aには1+1の演算結果である2が代入されています。

つまり、expect()で指定した変数aの値は2と等しいので、このテストコードはエラーを出さずパスします。

数式を1+2に変えてみると、変数aの値が3になり、2と等しい値ではなくなるので、エラーが出るのを確認できるでしょう。

<script>
    describe('入門用テストコード', function () {
        var obj1 = {foo: 'bar'},
            obj2 = {foo: 'bar'};
        
        it('値が等しいか', function () {
            expect(obj1).toEqual(obj2);
        });
    });
</script>

また、このようにオブジェクト内の値が等しいかを確認することもできます。

同じオブジェクトかを確認するテストコード - toBe()

同じオブジェクトであることを期待します。

<script>
    describe('入門用テストコード', function () {
        var obj1 = {'foo': 'bar'},
            obj2 = {'foo': 'bar'};

        it('同じオブジェクトか', function () {
            expect(obj1).toBe(obj2);
        });
    });
</script>

obj1とobj2は中身のキーと値は同一ですが、異なるオブジェクトです。

そのため、toEqual()とは違い、このテストコードを実行するとエラーを出します。

toBe()での指定をobj1に変えてみると同じオブジェクトなのでエラーを出さずパスします。

<script>
    describe('入門用テストコード', function () {
        var obj1 = {'foo': 'bar'},
            obj2 = {'foo': 'bar'};

        it('同じオブジェクトか', function () {
            var obj3 = obj1;

            expect(obj1).toBe(obj3);
        });
    });
</script>

このテストコードでは、obj3にobj1を代入しています。

そのため、obj1とobj3は同じオブジェクトであると扱われ、toBe(obj3)はエラーを出さずパスします。

定義されているか(undefinedでないか)を確認するテストコード - toBeDefined()

expect()で指定した変数などが定義されていることを期待します。

<script>
    describe('入門用テストコード', function () {
        it('定義されているか', function () {
            expect(a).toBeDefined();
        });
    });
</script>

このテストコードでは、どこにも変数aを定義していないので、エラーを出します。

定義されているかを確認するテストコードなので、toBeDefined()には何も指定しません。

<script>
    describe('入門用テストコード', function () {
        var a = 1;

        it('定義されているか', function () {
            expect(a).toBeDefined();
        });
    });
</script>

値を含むかを確認するテストコード - toMatch()

expect()で指定した内容にtoMatch()で指定した内容が含まれていることを期待します。

<script>
    describe('入門用テストコード', function () {
        var a = 'hoge';
        
        it('指定の内容を含むか', function () {
            expect(a).toMatch(/[a-z]+/);
            expect(a).toMatch('ho');
        });
    });
</script>

toMatch()では上の例のように正規表現を使うこともできます。

関数が例外を返すかを確認する - toThrow()

expect()で指定した関数が例外を返すことを期待します。

<script>
    describe('入門用テストコード', function () {
        function foo() {
            return 1+2;
        }

        function bar() {
            return a+1;
        }

        it('例外を返すか', function () {
            expect(bar).toThrow();
        });
    });
</script>

関数名だけをexpect()に指定します。

関数bar()を実行すると、内部で使用している変数aが未定義のため、Uncaught ReferenceErrorが発生します。

例外を返すことを期待しているので、テストコード的にはエラーを出さずパスします。

expect()にfooを指定すると、普通に1+2が演算されて数値の3を返すため、テストコード的にはエラーとなります。

その他のMatcher一覧

toBeNull()
nullを期待する
toBeTruthy()
値がtrueであることを期待する
toBeFalsy()
値がfalseであることを期待する
toContain()
指定した値が含まれていることを期待する(文字列など)
toBeLessThan()
指定した値がexpect()で指定した値よりも小さいことを期待する(以下ではない)
toBeGreaterThan()
指定した値がexpect()で指定した値よりも大きいことを期待する(以上ではない)

Matcherの否定 - not

上記Matcherの否定形を使用したい場合があるかと思います。

たとえば、値が等しくない状態でエラーを出さずにパスしたい場合などです。

その場合にはMatcherメソッドの前にnotを挟み込むことで否定形にすることができます。

<script>
    describe('入門用テストコード', function () {
        var a = 1+1;
        it('値が等しくないか', function () {
            expect(a).not.toEqual(3);
        });
    });
</script>

このテストコードでは、変数aの値は2であり、3と等しい値ではないため、エラーを出さずにパスします。

逆に1+2などで3と等しい値となった場合にはエラーを出します。

Jasmineによるテストコードの記述実例

ここまでの内容を総合的に実践してみましょう。

失敗するテストを書く

まずは失敗するテストコードを記述します。

……とはいっても、今回はコード本体をまだ書いてないので、この段階では何を書いてもエラーになるんですけどね。

<script>
    describe('Jasmine実践', function () {
        it('関数が例外を返さないか', function () {
            expect(sample).not.toThrow();
        });

        it('関数に引数を3つ渡し、返り値の配列要素数が1以上あるか', function () {
            expect(sample('foo', 'bar', 'baz').length).toBeGreaterThan(0);
        });
    });
</script>

テストがパスするような最小限のコード本体を書く

次にエラーが出ないようにコード本体を記述します。

コード本体はSpecRunner.htmlと同じディレクトリ階層にsample.jsというファイルを作成して記述し、次のようにscript要素のsrcでパスを指定します。

<script src="sample.js"></script>

sample.jsの中に、関数sample()を作成します。

/**
  * 引数3つを1つの配列にして返す関数
  */
function sample() {
    return ['foo', 'bar', 'baz'];
}

これでエラーを出さずにパスするはずです。

コードの重複を除去する(リファクタリング)

今回の実例では特に重複した内容はありませんが、定数を使っているので引数を配列化して返してみます。

/**
\* 引数3つを1つの配列にして返す関数
\*/
function sample(arg1, arg2, arg3) {
return arguments;
}

argumentsは関数に渡されている引数すべてを配列化しており、['foo', 'bar', 'baz']という配列になります。

Jasmineのテストコードについてもっと知りたい!

おそらく「ひとまずテストコードを書いてみる」という入門レベルへの導入までは本記事でカバーできていると思うのですが、本記事は入門用として考えていますので、ここで紹介していないMatcherメソッドの種類もいくつかあり、beforeEach()やafterEach()なども解説していません。

もっと詳しくお知りになりたい方は、Jasmineの公式サイトに全部掲載されていますので、ぜひそちらを参考に色々と試してみてください。