Skip to main content

JavaScriptで初めてのユニットテストを書く方法

著者

Raphael Mun

feature java dto

2022年10月28日

0 分で読めます

コードのテストは、セキュリティを確保するための第一歩です。その効果的な方法のひとつがユニットテストです。アプリ内の小さな関数がそれぞれ想定どおりに動作することを確認できます。特に、アプリが境界値や無効な入力、悪意のある可能性がある入力を受け取った場合に役立ちます。

ユニットテストを行う理由

ユニットテストにはさまざまなアプローチがあります。主な目的は次のとおりです。

  • 機能の検証:コードが正しく動作し、想定外の動作をしないことを確認できます。バグの多くは、こうした想定外の動作が原因です。

  • コードのデグレード防止:バグを見つけたら、その状況を確認するユニットテストを追加します。これにより、将来のコード変更でバグが再発するのを防げます。

  • コードのドキュメント化:適切なユニットテストを一式用意し、その結果を記録しておけば、アプリの想定される動作仕様を示せます。

  • アプリのセキュリティ確保:ユニットテストで、悪意のあるSQLインジェクションを可能にするものなど、悪用可能な脆弱性を確認できます。

ユニットテストの範囲設定と作成

ユニットテストフレームワークを使うと、テストをすばやく作成して自動化し、開発やデプロイのプロセスに組み込めます。こうしたフレームワークの多くは、フロントエンドとバックエンドの両方のJavaScriptコードをテストできます。

パフォーマンスに優れたユニットテストと、テストしやすいコードを書くための一般的なガイドラインを紹介します。

ユニットテストは短くシンプルに

ユニットテストに詰め込みすぎないようにしましょう。テストは数行程度のコードにし、アプリの短く機能的な単位を確認するものにします。

正常系と異常系の両方をテストする

関数が正常に実行されることを確認するテストは有用です。しかし、誤った使い方や境界条件で関数が適切に失敗するかどうかも確認する、より幅広いテストを作成すると、さらに効果的です。こうした異常系のテストは、関数が例外をスローすべき場合や、不正な形式のデータを受け取ったときの処理など、予期しない状況を想定するうえで特に役立ちます。

長く複雑な関数を分割する

多くのロジックを含む大きな関数はテストが困難です。処理が多すぎると、各変数を効果的にテストできません。関数が複雑すぎる場合は、個別にテストできるよう小さな関数に分割しましょう。

ネットワークやデータベースへの接続を避ける

ユニットテストはすばやく軽量に実行できる必要があります。しかし、ネットワーク呼び出しを行う関数や、ほかのアプリやプロセスに接続する関数は、実行に時間がかかります。そのため、多数の処理を同時に実行しにくくなり、コードが不安定になることもあります。ユニットテストでモックを使用すれば、ネットワークやデータベースへの接続をシミュレートでき、関数の残りの部分をテストできます。実際のネットワークやデータベースへの接続は、インテグレーションテストと呼ばれる別のテストプロセスで行えます。

ユニットテストの書き方

ユニットテストのベストプラクティスを確認したところで、JavaScriptで初めてのユニットテストを書いてみましょう。

このチュートリアルでは、ユニットテストで広く使われているMocha Frameworkを使用します。テストフレームワークごとに細かな違いはありますが、基本概念は共通しているため、基本を学べば簡単に別のフレームワークに移行できます。

このデモを進めるには、お使いのコンピューターにNode.jsがインストールされていることを確認してください。

新しいプロジェクトを作成する

まず、ターミナルまたはコマンドプロンプトを開き、新しいプロジェクトフォルダーに移動します。次に、npm init -yと入力して、その中に新しいNode.jsプロジェクトを作成します。

フォルダー内にpackage.jsonファイルが作成されます。これで、npm install -D mochaを実行してMochaを開発用の依存関係としてインストールできます。

次に、コードエディターでpackage.jsonファイルを開き、プレースホルダーのテストスクリプトをmochaに置き換えます。

  "scripts": {
    "test": "mocha"
  },

クラスを実装する

次に、ユニットテストの対象として、簡単な信号機システムを作成します。

プロジェクトディレクトリ内にtraffic.jsというファイルを作成し、TrafficLightクラスの次のコードを追加します。

class TrafficLight {
    constructor() {
        this.lightIndex = 0;
    }

    static get colors() {
        return [ "green", "yellow", "red" ];
    }
    get light() {
        return TrafficLight.colors[ this.lightIndex ];
    }
    next() {
        this.lightIndex++;
        // This is intentionally wrong!
        if( this.lightIndex > TrafficLight.colors.length ) {
            this.lightIndex = 0;
        }
    }
}

module.exports = TrafficLight;

このクラスは次の4つの要素で構成されています。

  • TrafficLight.colors:信号機の色を表す定数プロパティ。

  • lightIndex:現在の信号機の色のインデックスを保持する変数。

  • light:現在の信号機の色を文字列で返すクラスプロパティ。

  • next():信号機の色を次の色に切り替える関数。

最初のユニットテストを設定して追加する

コードにユニットテストを追加しましょう。

プロジェクトディレクトリ内にtestというフォルダーを作成します。Mochaはデフォルトでこのフォルダー内のユニットテストを検索します。次に、新しいtestフォルダー内にtraffic.test.jsというファイルを作成します。

次に、ファイルの先頭でTrafficLightクラスをインポートします。

const TrafficLight = require( "../traffic" );

テストにはassertモジュールも使用するため、コード内で読み込みます。

const assert = require( "assert" );

Mochaでは、describe()関数を使ってユニットテストをグループ化できます。次のように、このクラスのトップレベルのグループを設定します。

describe( "TrafficLight", function () {
});

次に、TrafficLightのグループ内にcolorsというサブグループを作成し、信号機の色を検証するユニットテストを追加します。

describe( "TrafficLight", function () {
    describe( "colors", function () {
    });
});

最初のユニットテストでは、colorsに緑、黄、赤の3つの状態だけがあることを確認します。テストはdescribe()グループ内でit()関数を使って定義します。次のようにテストを記述します。

describe( "TrafficLight", function () {
    describe( "colors", function () {
        it( "has 3 states", function () {
            const traffic = new TrafficLight();
            assert.equal( 3, TrafficLight.colors.length );
        });
    });
});

それでは、ユニットテストを実行して、パスするか確認しましょう。

ターミナルでnpm testを実行します。問題がなければ、Mochaにユニットテストの実行結果が表示されます。

TrafficLightの色をテストするMochaテストのターミナル出力。1件のテストが8ミリ秒で成功。

ユニットテストを追加する

これでプロジェクトでユニットテストを実行する準備が整いました。コードが正しく動作することを確認するため、テストを追加しましょう。

まずcolorsグループにユニットテストを追加し、信号機の色が正しく、順番どおりであることを検証します。実装例を紹介します。

it( "colors are in order", function () {
    const expectedLightOrder = [ "green", "yellow", "red" ];
    const traffic = new TrafficLight();
    for( let i = 0; i < expectedLightOrder.length; i++ ) {
        assert.equal( expectedLightOrder[ i ], TrafficLight.colors[ i ] );
    }
});

次に、next()関数をテストし、信号機の色が正しく切り替わることを確認します。新しいサブグループを作成して、2つのユニットテストを追加します。1つは色が正しい順序で変わることを、もう1つはredの後にgreenへ戻ることを確認します。

次のようにユニットテストを記述します。

describe( "next()", function () {
    it( "changes lights in order", function () {
        const traffic = new TrafficLight();
        for( let i = 0; i < TrafficLight.colors.length; i++ ) 
            assert.equal( traffic.light, TrafficLight.colors[ i ] );
            traffic.next();
        }
    });
    it( "loops back to green", function () {
        const traffic = new TrafficLight();
        // Change the light 3x to go from green -> yellow -> red -> green
        for( let i = 0; i < 3; i++ ) {
            traffic.next();
        }
        assert.equal( traffic.light, TrafficLight.colors[ 0 ] );
    });
});

テストを再実行すると、1つ失敗します。これはTrafficLightクラスにバグがあるためです。

MochaのJavaScriptテストのターミナル出力。3つのテストが成功し、信号機に関するアサーションが1つ失敗しています。

バグを修正する

もう一度TrafficLightクラスのコードを開き、next()関数内にある// This is intentionally wrong!というコメントを探します。

ユニットテストの結果から、この関数が正しくgreenに戻れていないことがわかります。現在のコードは、lightIndexの値が信号機の色の数を超えたかどうかを確認していますが、インデックスは0から始まります。そこで、色の数と同じ値に達した時点でループを最初に戻す必要があります。信号機の色のリストの長さと等しいときにlightIndexの値を0にリセットするよう、コードを更新しましょう。

// This is intentionally wrong!
if( this.lightIndex === TrafficLight.colors.length ) {
    this.lightIndex = 0;
}

これで、すべてのユニットテストがパスするはずです。さらに、TrafficLightクラスがリファクタリングされたり、大幅に変更されたりしても、ユーザーに届く前にユニットテストでこのバグを検出できます。

TrafficLightの色に関するMochaのユニットテストで、4件すべて成功したことを示すターミナル出力。

まとめ

ユニットテストは簡単に導入でき、ソフトウェア開発に効果的なツールです。バグを早期に発見して再発を防ぐことで、プロジェクトの規模や複雑さが増しても、特に大規模な開発チームで、管理や保守をしやすくします。また、このような自動テストがあれば、新しいコードが正しく動作するか心配することなく、開発者はコードのリファクタリングや最適化を行えます。

ユニットテストは開発プロセスに欠かせない要素であり、より優れた、より安全なJavaScriptアプリの構築に役立ちます。

テストを楽しみましょう!

Capture the Flagを始めよう

オンデマンドのバーチャル入門ワークショップを見て、Capture the Flagの課題の解き方を学びましょう。