TypeScriptのユニットテスト環境を構築してみた

2022.05.20

はじめに

アノテーションの髙嶋です。

私は業務でよくGoogle Apps Script(以降、GAS)を書いています。 ただGASの実行環境で実行できるテスト用フレームワークがなく、関数単位での動作確認がしづらい状態です。

なので、多少でもローカル環境でテストを実施できるようにユニットテスト実行環境を構築したので、その手順を記述します。
ただ、ユニットテストはTypeScriptで書いた状態で実行しています。 そのため、GASの関数部分はモックする必要があり、実行環境での最終的な動作確認は別途必要になります。

なんでテストが必要なの?

そもそもなんでテストをする必要があるのか。

よく聞く話かもしれませんが、プログラムは考えたとおりに動くわけではなく、書かれたとおりに動きます。 ですので、プログラムが仕様どおり(考えたとおり)に動作していることを確認するためにはテストの実施が必要です。

テストをコードとして書いておくと、同じテストが何回も実行できるようになるため、修正後の動作確認が簡単に確認できるようになります。 ただ、テストコードを書くことにもコストはかかるので、プログラムの規模によってはテストコードは書かないという選択もありだと思います。

テスト環境構築

今回はテストフレームワークとしてJestを使用しています。
Jestを使っている理由は、最初にテストフレームワークを調べた時に"なんとなくこれが良さそうかも?"と思ったからです。

もし"このフレームワークがおすすめだよー"というのがあれば教えてください。

前提条件

今回の環境はWindowsでWSL2を使用し、Dockerのコンテナ環境を立ち上げています。
また、Visual Studio Code(以降、VSCode)からコンテナ環境へ接続して操作しています。

OS:Windows 10 Pro (21H1)
VSCode:1.66.2
WSLディストリビューション:Ubuntu 20.04
Dockerイメージ:node:17

Dockerコンテナ内にインストールされているアプリケーションのバージョンです。

node:v17.9.0
npm:8.7.0

※コンテナ起動直後のnpmのバージョンは8.5.5でしたが、通知が出たのでアップデートしています。

環境構築手順

npmの設定初期化

npmの設定に関する初期化処理を実行します。実行が完了すると"package.json"というファイルが作成されます。

npm init -y

パッケージインストール

ユニットテストの実行に必要となるパッケージをインストールします。

npm install typescript @types/jest jest ts-jest

今回インストールされたバージョンは下記のとおりです。

tsc:Version 4.6.3
jest:27.5.1

TypeScriptの設定初期化

TypeScriptの設定に関する初期化処理を実行します。実行が完了すると"tsconfig.json"というファイルが作成されます。

npx tsc --init

テストの設定ファイル作成

テストの設定を記述したファイルを作成します。
※個々の設定項目の内容に関しては今回は触れません

jest.config.json

{
    "globals": {},
    "moduleNameMapper": {},
    "moduleDirectories": [
        "src",
        "node_modules"
    ],
    "moduleFileExtensions": [
        "js",
        "ts"
    ],
    "preset": "ts-jest",
    "testEnvironment": "node"
}

これで実行環境の構築は完了です。

テストコード準備

実行するテストコードを準備します。
今回はサンプルとして下記のようなコードを作成しています。それぞれクラスの場合と関数の場合を記述しています。 テストコードの書き方に関してはこちらを参考にしてください。

【ソースコード】

src/sampleClass.ts

export class SampleClass {
    public tasu(
        a: number,
        b: number
    ): number {
        return a + b;
    }
}

src/sampleFunction.ts

export function hiku(
    a: number,
    b: number
): number {
    return a - b
}

【テストコード】

__tests__/sampleClass.test.ts

import { SampleClass } from '../src/sampleClass';

let hoge = new SampleClass();

describe('sampleClassのテスト', () => {
    test('sample1', () => {
        expect(hoge.tasu(1, 1)).toBe(2);
    });

    test('sample2', () => {
        expect(hoge.tasu(2, 1)).toBe(4);
    });
});

__tests__/sampleFunction.test.ts

import { hiku } from '../src/sampleFunction';

describe('sampleFunctionのテスト', () => {
    test('sample1', () => {
        expect(hiku(2, 1)).toBe(1);
    });

    test('sample2', () => {
        expect(hiku(1, 2)).toBe(-1);
    });
});

テスト実行

ここまでの手順を実行すると、下記のようなフォルダ構成になっているはずです。 もし足りないファイル/フォルダがあれば、前述した手順を再度確認してください。

./
├ __tests__
|  ├ sampleClass.test.ts
|  └ sampleFunction.test.ts
├ node_modules
├ src
|  ├ sampleClass.ts
|  └ sampleFunction.ts
├ jest.config.json
├ package-lock.json
├ package.json
└ tsconfig.json

構築した環境で実際にユニットテストを実行するため、下記コマンドを実行します。

npx jest

そうするとコンソールにテスト結果が表示されます。

はい、テストが失敗しております。。。 "sampleClass.test.ts"の11行目の検証で、メソッドの戻り値が想定と実際で違うので失敗と判定されています。

これに関しては、想定値の記述が誤っているので、テストコードを下記のように修正して再実行します。

__tests__/sampleClass.test.ts

test('sample2', () => {
    expect(hoge.tasu(2, 1)).toBe(3);
});

今度はすべてのテストが成功しました。

このようにコマンド1発で同じテストが何度でも実行できるので、ソースコードを修正するたびに実行すれば、簡単に動作結果を確認できます。

もし特定のテストだけを実行したい場合は、コマンドにテストファイルのパスを指定することで、対象のテストだけを実行することができます。
例)

npx jest __tests__/sampleFunction.test.ts

最後に

テストコードを書いていて思うのは、書くのが大変ということです。

必要なテストケースをコードとして書くのも大変ですし、書いて実行しても意図した結果にならなくて「なんで動かないの!」となったりします。。。
(実装ではなく、テストコードの記述が間違っていることも多々あります)

ただ、書くのが大変なのは私がテストコードを書きなれていないためで、数をこなせば慣れると信じています。なので、これからもがんばってテストコードを書いていきます!

最後までお読みいただきありがとうございました。 それでは、また次の記事でお会いしましょう。

アノテーション株式会社について

アノテーション株式会社は、クラスメソッド社のグループ企業として「オペレーション・エクセレンス」を担える企業を目指してチャレンジを続けています。「らしく働く、らしく生きる」のスローガンを掲げ、様々な背景をもつ多様なメンバーが自由度の高い働き方を通してお客様へサービスを提供し続けてきました。現在当社では一緒に会社を盛り上げていただけるメンバーを募集中です。少しでもご興味あれば、アノテーション株式会社WEBサイトをご覧ください。