ユニットテストを書いてみよう
今回は人気のプログラミング言語であるTypeScriptを用いて、ユニットテストの実例を紹介します。
TypeScriptでテストを実施する場合、Jestというテストフレームワークを利用するのが一般的です。しかし、セットアップに少し手間がかかるため、今回は簡単に環境構築ができるDeno(ディノ)を使用します。Denoにはテストランナーが同梱されており、特別なセットアップが不要であるためです。
ただ、本記事では特定のプログラミング言語やテストフレームワークに限定されない考え方を紹介すること、また後述するように「準備、実行、値の検証」というテストの構成自体は同じであるため、ツールの違いによる大きな差異は特にないと考えていただいて問題ありません。
Deno をインストールする
ここからは実際に手を動かしましょう。なお、以下の記述は執筆時点(2024年7月)のものであるため、環境構築のための最新情報はDenoの公式ドキュメントをご覧ください。
まずDenoをインストールします。Macの方はターミナルから以下のコマンドを実行してください。
curl -fsSL https://deno.land/install.sh | sh
Windowsの方は以下のコマンドです。
irm https://deno.land/install.ps1 | iex
筆者はMacを用いているので、以下はMacのコマンドで記述していきます。
次に$ deno --version
のコマンドを実行してください。以下のような結果が表示されればインストールは完了です。
$ deno --version deno 1.44.1 (release, aarch64-apple-darwin) v8 12.6.228.3 typescript 5.4.5
各バージョンについて、Denoはv1.43.3、V8(JavaScript のコードを解釈し、実行するエンジン)はv12.6.228.3、TypeScript は v5.4.5 がインストールされていることがわかります。今回は基本機能のみを用いるため、もしお手元のバージョンが違っていても動作に問題はありません。
テスト対象のコードを書く
では、コードを書いていきましょう。まずテスト対象となる関数を書いていきます。
unit-test
というディレクトリを作成し、ここをルートディレクトリとします。
次に、unit-test
配下にunit.ts
ファイルを作成します。今回は足し算の関数をテストするため、以下のように関数を定義します。
export function add(a: number, b: number) { return a + b }
これでテスト対象となる関数の準備ができました。テストで動作を確認する対象は、一般的にプロダクションコードやテスト対象と呼ばれるので覚えておきましょう。
テストコードを書く
次にテストコードを書きます。テストコードはテスト対象とは別のファイルに書くことがほとんどのプログラミング言語で一般的です(ただし、Rustは同じファイルにテスト対象もテストも書くそうです)。
今回はunit.test.ts
ファイルを作成し、以下のようにテストを書いてみましょう。
import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts"; import { add } from "./unit.ts"; // 1 Deno.test("1 + 2 は 3 である", () => { // 2 const x = add(1, 2); // 3 assertEquals(x, 3); // 4 });
テストコードを丁寧に解説します。 まず、コードコメント1の行のimport
は、TypeScriptのモジュールシステム上の記法で、別ファイルで定義された変数、関数、クラスを呼び出すことができるものです。
unit.ts
ファイルでexport function add(..)
のようにexport
と記述したため、関数addは別ファイルであるunit.test.ts
ファイルからも呼び出すことができます。
次に、コードコメント2の行のDeno.test
はテストケースを記述するための関数です。第一引数でテストケース名を受け取り、第二引数では関数形式でテスト内容を受け取ります。
コードコメントの3の行では、関数add
に数値1、2を渡しています。実行結果は変数x
に格納されています。 コメントの4の行のassertEqualsはDenoが用意している関数です。これは、テストで自分が期待する結果と実際の結果を比較するために使われます。このような特別な関数は一般的にアサーション(assertion)と呼ばれます。
中でも、assertEqualsは引数として渡した二つの値が同じであることを確認する関数です。ここでは変数x
が数値の3と一致するかチェックしています。一般にこのx
は「実際の値(actual)」、3は「期待する値(expected)」と呼ばれます。
テストを実行して結果を確認する
テストコードを書き終えたので、テストを実行してみましょう。unit-test
ディレクトリに移動し、$ deno test
を実行してください。
$ deno test Check file:///Users/panda/unit-test/unit.test.ts running 1 test from ./unit.test.ts 1 + 2 は 3 である ... ok (0ms) ok | 1 passed | 0 failed (1ms)
ok という表示が出ることで、テストは無事に実行され、テスト対象が期待通りの動きをすることがわかりました。このことを「テストが通る(Pass した)」といいます。
もし実際の値と期待する値が異なる場合にどう動くか確かめてみましょう。unit.ts
の関数add
の足し算をする箇所を引き算に変えてみます。
export function add(a: number, b: number) { return a - b // add関数なのに引き算をしている }
このコードで$ deno test
を実行してみます。
$ deno test Check file:///Users/panda/unit-test/unit.test.ts running 1 test from ./unit.test.ts 1 + 2 は 3 である ... FAILED (1ms) ERRORS 1 + 2 は 3 である => ./unit.test.ts:4:6 error: AssertionError: Values are not equal. [Diff] Actual / Expected - -1 + 3 throw new AssertionError(message); ^ at assertEquals (https://deno.land/std@0.224.0/assert/assert_equals.ts:52:9) at file:///Users/panda/unit-test/unit.test.ts:6:3 FAILURES 1 + 2 は 3 である => ./unit.test.ts:4:6 FAILED | 0 passed | 1 failed (2ms) error: Test failed
実行結果は少し長いですが、恐れずに主要な部分を確認していきましょう。
1 + 2 は 3 である ... FAILED (1ms)
まず、上記の部分からテストがpassしたのではなくfailedになったことがわかります。日本語ではこれを「テストが失敗した」と表現します。
error: AssertionError: Values are not equal. [Diff] Actual / Expected - -1 + 3
次にこの出力内容を見ると実行結果は-1である一方、期待する値が3であるため値がズレているということがわかります。よって、テストコードの実装者は数値の3が返ってくることを期待したのに実際は-1が返ってきたということがわかります。これでテストが失敗したときどのようなことが起きるかがわかりました。この情報から、テスト対象のコードにバグが含まれていることがわかります。
最後に、テストを通すためにadd
関数を元の足し算に戻して$ deno test
を再度実行しましょう。
$ deno test running 1 test from ./unit.test.ts 1 + 2 は 3 である ... ok (0ms) ok | 1 passed | 0 failed (1ms)
なお、add
関数のみだとロジックが単純すぎてテストコードのイメージを持ちにくいため、記事の最後にFizzBuzzの実装とそのテストを掲載します。よければそちらもご覧ください。