JavaScriptで自動テスト導入
 Author: 水卜

そもそもなぜテストを書くか

個人で開発をしていると中々やらないのかなという印象です。
プログラムが意図した挙動になっているかを確認するのがテストですが、テストを書くことはそれ以上の意味を持ちます。

きれいなテストは動く仕様書と化します。チーム開発では特にメリットがでかい。
例えば、誰かが書いたコードの改修を引き継いだとします。
中身を見るのには大変な労力が要りますが、テストコードがあればまずそれを見ます。

const index = require('./index');

describe('FizzBuzz', () => {
    describe('3の倍数でFizz, 5の倍数でBuzz, 3の倍数かつ5の倍数でFizzBuzzを返す', () => {
        it.each([
            ['3  -> Fizz', 3, 'Fizz'],
            ['5  -> Buzz', 5, 'Buzz'],
            ['15 -> FizzBuzz', 15, 'FizzBuzz'],
            ['6  -> Fizz', 6, 'Fizz'],
            ['10 -> Buzz', 10, 'Buzz'],
            ['30 -> FizzBuzz', 30, 'FizzBuzz'],
        ])('%s', (title, n, expected) => {
            expect(index.fizzbuzz(n)).toBe(expected);
        });
    });
});

もしこんなテストコードがあったとしたらどうでしょう。
要件が簡単なので当然かもしれませんが、コードの振る舞いが頭に入ってきます。

expect(index.fizzbuzz(n)).toBe(expected);

実行部分を見れば、ああこう使えばいいのね、と使い方も分かります。
もし引き継いだコードにテストがなければ、コードを見るより先に自分でテストを書いてから改修に踏み切る方が多分楽です。

他人の書いたコードなど信用できませんが、分かりやすく書かれたテストがあれば信用できます。

Jestを使ってみる

jestはFacebookが開発したOSSのJavaScriptテストフレームワークです。

ドキュメントはこちら
https://jestjs.io/docs/ja/getting-started

jestのインストール

npm install jest

テストコードを書く

ためしにFizzBuzzのテストを書いてみました。
以下をindex.spec.jsとして保存します。

const index = require('./index');

describe('FizzBuzz', () => {
    describe('3の倍数でFizz, 5の倍数でBuzz, 3の倍数かつ5の倍数でFizzBuzzを返す', () => {
        it.each([
            ['3  -> Fizz', 3, 'Fizz'],
            ['5  -> Buzz', 5, 'Buzz'],
            ['15 -> FizzBuzz', 15, 'FizzBuzz'],
            ['6  -> Fizz', 6, 'Fizz'],
            ['10 -> Buzz', 10, 'Buzz'],
            ['30 -> FizzBuzz', 30, 'FizzBuzz'],
        ])('%s', (title, n, expected) => {
            expect(index.fizzbuzz(n)).toBe(expected);
        });
    });
});

実装する

FizzBuzzはその気になれば1行で書けたりしますが、テストが本題なので実装はこんなもんで。

'use strict';

const fizzbuzz = n => {
    if (n % 3 === 0 && n % 5 === 0) {
        return 'FizzBuzz';
    } else if (n % 3 === 0) {
        return 'Fizz';
    } else if (n % 5 === 0) {
        return 'Buzz';
    }
};

module.exports = {fizzbuzz};

テスト実行

jest index.spec.js

結果は以下

$ jest index.spec.js
 PASS  ./index.spec.js (6.132s)
  FizzBuzz
    3の倍数でFizz, 5の倍数でBuzz, 3の倍数かつ5の倍数でFizzBuzz
      √ 3  -> Fizz (7ms)
      √ 5  -> Buzz (1ms)
      √ 15 -> FizzBuzz (1ms)
      √ 6  -> Fizz (1ms)
      √ 10 -> Buzz
      √ 30 -> FizzBuzz (1ms)

Test Suites: 1 passed, 1 total
Tests:       6 passed, 6 total
Snapshots:   0 total
Time:        11.594s
Ran all test suites matching /index.spec.js/i.

toEqual()とtoBe()の違い

toBe()は===でチェックします。

const a = {};
const b = {};
expect(a).toBe(b);

こんな時、aとbは異なるインスタンスなのでテストは失敗します。
toBe()はstring, number, booleanなどのプリミティブ型の比較で使用し、オブジェクトの比較はtoEqual()を使うとよいでしょう。

const a = {};
const b = {};
expect(a).toEqual(b);