🔍

個人的テストコードの書き方

に公開

目次

  1. 背景
  2. 環境
  3. テストコードの書き方
  4. テストコードを書く時のTips
  5. まとめ

背景

皆さんテストコードは書いてますか?

私の周りのエンジニアにはテストコードって何?テストコードは知ってるけど、書いてことないし難しそうという方がいました。
そんなエンジニアの方々にテストコードを普及したいと思い、私の個人的なテストコードの書き方を書いていこうと思います。

基本的にどのテスティングフレームワークでも応用可能なアイディアになっていると思いますので、ぜひ参考にしてみてください。

環境

今回テスティングフレームワークはJestに使用し、テストコードはTypescriptで記述していきたいと思います。
実行環境はdocker-composeを使用します。

各種設定ファイルの内容を記載します。

compose.yml
services:
  app:
    container_name: app
    image: node:lts
    tty: true
    volumes:
      - ./:/app
    working_dir: /app
package.json
{
  "name": "app",
  "version": "1.0.0",
  "main": "index.ts",
  "license": "MIT",
  "devDependencies": {
    "@types/jest": "^29.5.0",
    "jest": "^29.5.0",
    "ts-jest": "^29.0.5",
    "typescript": "^5.0.2"
  }
}
tsconfig.json
{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}
jest.config.js
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

これらの設定でプロジェクトディレクトリにて下記のコマンドを使用して環境を作成します。

$ docker compose up -d --build

[+] Running 1/1
 ⠿ Container app  Started

$ docker exec app yarn install

yarn install v1.22.19
[1/4] Resolving packages...
success Already up-to-date.
Done in 0.23s.

テストコードの書き方

まずはテスト対象のクラスファイルを作成します。
関数のテストをしたい場合は適宜、クラスを関数に読み替えて下さい。

$ docker exec app touch calcutation.ts
calculation.ts
export class Calculation {
  public add10(value: number): number {
    return value + 10;
  }
}

引数に10を足して返すメソッドを作成しました。
これからこのメソッドのテストコードを書きたいと思います。

$ docker exec app touch calculation.test.ts
calculation.test.ts
test("Calculationクラスのadd10メソッドのテスト", () => {});

1. 準備を行う

手動でのテストでもテストの前には準備が必要だと思います。
テストコードでも同様に前準備を行いましょう。
ここで言う前準備とはテストデータの用意や、クラスのインスタンス化などです。

calculation.test.ts
test("Calculationクラスのadd10メソッドのテスト", () => {
  // 1. 準備
  const arg = 1; // テストデータの用意(DBも含めたテストの場合はレコード)
  const calculation = new Calculation(); // テスト対象クラスのインスタンス化
});

2. 期待値を設定する

テストを実行した場合の期待結果を予め決めておきましょう。

calculation.test.ts
test("Calculationクラスのadd10メソッドのテスト", () => {
  // 1. 準備
  const arg = 1; // テストデータの用意(DBも含めたテストの場合はレコード)
  const calculation = new Calculation(); // テスト対象クラスのインスタンス化
  // 2. 期待値の設定
  const expected = 11;
});

3. テスト対象のメソッドの実行

準備したデータを使用してテスト対象のメソッドを実行します。

calculation.test.ts
test("Calculationクラスのadd10メソッドのテスト", () => {
  // 1. 準備
  const arg = 1; // テストデータの用意(DBも含めたテストの場合はレコード)
  const calculation = new Calculation(); // テスト対象クラスのインスタンス化
  // 2. 期待値の設定
  const expected = 11;
  // 3. テスト対象のメソッドの実行
  const received = calculation.add10(arg);
});

4. 実行結果と期待値の比較

メソッドの実行結果と期待結果を比較して一致しているかを確認します。

calculation.test.ts
test("Calculationクラスのadd10メソッドのテスト", () => {
  // 1. 準備
  const arg = 1; // テストデータの用意(DBも含めたテストの場合はレコード)
  const calculation = new Calculation(); // テスト対象クラスのインスタンス化
  // 2. 期待値の設定
  const expected = 11;
  // 3. テスト対象のメソッドの実行
  const received = calculation.add10(arg);
  // 4. 実行結果と期待値の比較
  expect(received).toEqual(expected);
});

実際にテストコードを実行したいと思います。

$ docker exec app yarn jest
PASS ./calculation.test.ts
  ✓ Calculationクラスのadd10メソッドのテスト (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        4.021 s
Ran all test suites.
Done in 7.86s.

テストが成功しました。

改めて、テストコードの書き方をまとめると以下の通りです。

  1. 準備を行う
  2. 期待値を設定する
  3. テスト対象のメソッドの実行
  4. 実行結果と期待値の比較

テストコードを書く時のTips

1. エラーのテストも行う

これはテストコードに限らずテスト全般に言えることですが、上手くいくテスト(正常系)だけではなく、上手くいかない場合のテスト(異常系)も実施するようにしましょう。

例えば、10を引数で割り算するメソッドがあった場合に、引数が0である場合のテストを実施することで、実装漏れやエラーを適切に処理出来るかを確認することが出来ます。

calculation.ts
export class Calculation {
  public add10(value: number): number {
    return value + 10;
  }
  
  public division(value: number): number {
    return 10 / value;
  } // 追加
}
calculation.test.ts
test("Calculationクラスのdivisionメソッドのテスト", () => {
  // 1. 準備
  const arg = 0;
  const calculation = new Calculation();
  // 2. 期待値の設定
  // 今回は何かしらのエラーが発生することを想定するので期待値は設定しない。
  // 3. テスト対象のメソッドの実行
  const received = calculation.division(arg);
  // 4. 実行結果と期待値の比較
  expect(received).toThrowError;
});
$ docker exec app yarn jest

✓ Calculationクラスのadd10メソッドのテスト (2 ms)
✓ Calculationクラスのdivisionメソッドのテスト

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.161 s
Ran all test suites.
Done in 5.82s.

2. 適切なテスティングフレームワークの使い方を行う

これまで実行結果と期待値の比較にはjestのtoEqualメソッドを使用してきました。
jestにはtoEqualメソッド以外にもtoBeメソッドでも値の比較が可能です。
しかし、適切に使用しないと上手くいくはずのテストコードも上手くいきません。

掛け算を行うmultipleメソッドを追加して、これまでの手順通りにテストコードを記述します。
この時、結果の比較にはtoBeメソッドを使用してみます。

calculation.ts
export class Calculation {
  public add10(value: number): number {
    return value + 10;
  }

  public division(value: number): number {
    return 10 / value;
  }

  public multiple(x: number, y: number): { result: number } {
    return { result: x * y };
  } // 追加
}
calculation.test.ts
test("Calculationクラスのmultipleメソッドのテスト", () => {
  // 1. 準備
  const argX = 1;
  const argY = 2;
  const calculation = new Calculation();
  // 2. 期待値の設定
  const expected = { result: 2 };
  // 3. テスト対象のメソッドの実行
  const received = calculation.multiple(argX, argY);
  // 4. 実行結果と期待値の比較
  expect(received).toBe(expected); // toBeメソッドを使用
});
$ docker exec app yarn jest

✓ Calculationクラスのadd10メソッドのテスト (2 ms)
✓ Calculationクラスのdivisionメソッドのテスト
✕ Calculationクラスのmultipleメソッドのテスト (4 ms)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 2 passed, 3 total
Snapshots:   0 total
Time:        3.251 s
Ran all test suites.
error Command failed with exit code 1.

一見成功しそうなテストコードですが、失敗してしまいました。
これはtoBeメソッドはオブジェクトの比較に使用出来ないためです。
代わりにtoEqualメソッドを使用してみましょう。

calculation.test.ts
test("Calculationクラスのmultipleメソッドのテスト", () => {
  // 1. 準備
  const argX = 1;
  const argY = 2;
  const calculation = new Calculation();
  // 2. 期待値の設定
  const expected = { result: 2 };
  // 3. テスト対象のメソッドの実行
  const received = calculation.multiple(argX, argY);
  // 4. 実行結果と期待値の比較
  // expect(received).toBe(expected);
  expect(received).toEqual(expected); // toEqualメソッドを使用
});
$ docker exec app yarn jest

✓ Calculationクラスのadd10メソッドのテスト (2 ms)
✓ Calculationクラスのdivisionメソッドのテスト
✓ Calculationクラスのmultipleメソッドのテスト

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        3.523 s
Ran all test suites.
Done in 6.21s.

テストが成功しました。

このようにテスティングフレームワークでの値の比較には様々な方法があるので、
公式ドキュメントなどを読んで出来るだけ適切な比較を行うようにしましょう。

3. テストカバレッジを計測する

テストコードを記述するとテストコードがどれだけソースコードを網羅しているかを表すテストカバレッジを計測することが出来ます。
テストカバレッジの網羅率が高いことはそのままソースコードの多くの部分がテストされていることになるので、確認してみましょう。

分岐のあるメソッドをCalculationクラスに追加します。

calculation.ts
export class Calculation {
  public add10(value: number): number {
    return value + 10;
  }

  public division(value: number): number {
    return 10 / value;
  }

  public multiple(x: number, y: number): { result: number } {
    return { result: x * y };
  }

  public sub10(value: number): number {
    if (value === 0) {
      return 0;
    }
    if (value < 10) {
      return value;
    }
    return value - 10;
  } // 追加
}
calculation.test.ts
test("Calculationクラスのsub10メソッドのテスト", () => {
  // 1. 準備
  const arg = 9;
  const calculation = new Calculation();
  // 2. 期待値の設定
  const expected = 9;
  // 3. テスト対象のメソッドの実行
  const received = calculation.sub10(arg);
  // 4. 実行結果と期待値の比較
  expect(received).toEqual(expected);
});
$ docker exec app yarn jest --coverage

✓ Calculationクラスのadd10メソッドのテスト (2 ms)
✓ Calculationクラスのdivisionメソッドのテスト
✓ Calculationクラスのmultipleメソッドのテスト (1 ms)
✓ Calculationクラスのsub10メソッドのテスト

----------------|---------|----------|---------|---------|-------------------
File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------------|---------|----------|---------|---------|-------------------
All files       |   77.77 |       50 |     100 |   77.77 |                   
 calculation.ts |   77.77 |       50 |     100 |   77.77 | 16,21             
----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        4.11 s
Ran all test suites.
Done in 6.72s.

jestの機能でテストカバレッジを計測しました。
今回のテストコードではcalculation.tsの16,21行目が網羅出来ていないようです。

このようにカバレッジを計測することで適切にテストが出来ているかを確認することが可能です。

まとめ

以上、個人的なテストコードの書き方をまとめてみました。

テストコードを書くことでより保守性の高いコード、より品質の高いコードを書くことが可能になるので、ぜひ皆さんテストコードを書いて開発していきましょう。

今回作成したソースコードのリポジトリはこちらです。

Discussion