テストコード / ユニットテスト
Test Code / Unit Test
概要(サマリー)
テストコードは、作成したプログラムが期待どおりに動くかを確認するために書く検証用のコードである。
人が画面を開いてボタンを押す手動確認とは違い、テストコードでは「この入力なら、この結果になるべき」という条件をコードとして書く。テストツールを実行すると、その条件を満たしているかを自動で確認できる。
ユニットテスト(単体テスト)は、関数やクラスなど、小さな単位に絞って動作を確認するテストである。小さな部品ごとに確認できるため、バグの原因を見つけやすく、リファクタリングや機能追加を安全に進めやすくなる。
詳細解説
テストコードとは何か
プログラムを作ったあとには、正しく動くかを確認する必要がある。
最初は、ブラウザを開いて画面を操作したり、コマンドを実行したりして確認することが多い。しかし、機能が増えると、毎回すべてを手で確認するのは時間がかかる。
テストコードは、この確認作業の一部をコード化するためのものだ。たとえば、「税込価格を計算する関数に1000を渡したら1100が返るべき」といったルールを、プログラムとして書いておく。
テストコードを実行すると、期待した結果と実際の結果を比較し、合っていれば成功、違っていれば失敗として知らせてくれる。
ユニットテストとは
ユニットテストは、プログラムの小さな部品を単体で確認するテストである。
たとえば、次のような処理はユニットテストの対象になりやすい。
ユニットテストは、画面全体やデータベース接続まで含めて確認するものではない。できるだけ小さな単位を切り出し、その部品が期待どおり動くかを確認する。
このため、テストが失敗したときに「どの処理が壊れたのか」を見つけやすい。
コード例
次の例では、2つの数値を足す関数と、その動作を確認するテストコードを分けて書いている。
テスト対象のコード
function sum(a, b) {
return a + b;
}
module.exports = sum;
テストコード
const sum = require('./sum');
test('1と2を足すと3になる', () => {
expect(sum(1, 2)).toBe(3);
});
test('-1と1を足すと0になる', () => {
expect(sum(-1, 1)).toBe(0);
});
この例では、sum(1, 2) の結果が 3 になること、sum(-1, 1) の結果が 0 になることを確認している。
もし sum 関数を書き換えたあとに結果が変わってしまえば、テストが失敗し、意図しない変更に気づける。
テストコードのメリット
テストコードを書くと、次のような利点がある。
- 修正によって既存機能が壊れていないか確認しやすい
- バグの原因になった条件を再現しやすい
- 仕様の例として読める
- リファクタリングを安全に進めやすい
- CI/CDで自動実行し、問題のあるコードを早く検出できる
特に、過去に発生したバグを修正するときは、そのバグを再現するテストを先に追加しておくとよい。修正後に同じバグが再発した場合、テストが失敗して知らせてくれる。
ユニットテスト以外のテスト
自動テストには、ユニットテスト以外にもいくつかの種類がある。
- 結合テスト: 複数の部品を組み合わせたときに正しく動くかを確認する
- E2Eテスト: ユーザーの操作に近い形で、画面全体の流れを確認する
- スナップショットテスト: 出力結果や画面構造が以前と変わっていないかを確認する
- 回帰テスト: 過去に直したバグが再発していないかを確認する
ユニットテストは基本だが、それだけですべてを確認できるわけではない。アプリの重要度や変更リスクに応じて、複数のテストを組み合わせる。
テストコードにも限界がある
テストコードは便利だが、万能ではない。
テストは、書いた条件しか確認できない。たとえば、正常な入力だけをテストしていて、不正な入力、空文字、境界値、通信失敗などを確認していなければ、その部分のバグは見逃される。
また、テストコード自体が間違っていることもある。期待値を誤って書いていたり、本来確認すべき動作を確認していなかったりすれば、テストが成功しても安心できない。
テストコードは、手動確認やレビューを置き換えるものではなく、開発の安全性を上げるための土台と考えるとよい。
AIコーディングとの関係
AIコーディングでは、テストコードの重要性がかなり高い。
AIは素早くコードを生成できる一方で、仕様の読み違い、境界値の見落とし、既存コードとの不整合を起こすことがある。テストコードがあれば、AIが生成したコードが期待どおりに動くかを機械的に確認しやすい。
AIに依頼するときは、実装だけでなくテストも一緒に依頼するとよい。
この関数のユニットテストを作成してください。
正常系、空文字、不正な入力、境界値を含めてください。
既存のテストスタイルに合わせてください。
また、テストが失敗した場合は、失敗ログをAIに渡して修正を依頼できる。ただし、AIがテストを通すために実装ではなくテスト側を不適切に変えることもあるため、「仕様を変えずに実装を修正してください」と明示するとよい。
よくある勘違い
テストコードを書けばバグはなくなる?
なくならない。
テストコードは、書いた条件の範囲でしか確認できない。テストされていない条件や、仕様そのものの誤りまでは自動で見つけられない。
テストはバグを減らすための強力な手段だが、レビュー、設計、手動確認、ユーザー視点のチェックも必要である。
テストコードを書くと開発が遅くなる?
短期的には、テストを書く分だけ時間が増えることがある。
しかし、開発が続くほど、テストコードは手戻りを減らす。修正のたびに全機能を手で確認する時間を減らせるため、中長期では開発を速くすることが多い。
特に、壊れてはいけない計算処理、入力チェック、権限判定、APIの変換処理などは、テストを書く価値が高い。
ユニットテストだけあれば十分?
十分とは限らない。
ユニットテストは小さな部品の確認には強いが、画面全体の流れ、複数機能の連携、実際のユーザー操作までは確認しきれない。必要に応じて、結合テストやE2Eテストも組み合わせる。
テストコードは本番サーバーで動かすもの?
通常は違う。
テストコードは、開発者のPC、CI環境、ビルド前の検証環境などで実行する。本番公開用の成果物には、通常テストコードやテスト用ライブラリを含めない。
まとめ
- テストコードは、プログラムの動作をコード化した条件で自動確認するための検証用コードである。
- ユニットテストは、関数やクラスなど小さな部品を単体で確認するテストである。
- テストコードがあると、修正によるバグの再発や既存機能の破損に気づきやすい。
- テストは書いた条件しか確認できないため、過信せずレビューや手動確認も組み合わせる。
- AIコーディングでは、実装と一緒にテストコードも作らせると、出力の妥当性を確認しやすい。
情報ソース
より詳しくAIに聞いてみよう
- ユニットテスト、結合テスト、E2Eテストの違いを初心者向けに説明してください。
- JavaScriptで関数のユニットテストを書く基本的な流れを教えてください。
- バグ修正時に、再発防止のためのテストコードをどう書けばよいか教えてください。
- AIにテストコードを生成してもらうとき、正常系と異常系を漏れなく指定するプロンプト例を教えてください。
- テストが通っているのにバグが出る理由と、テスト設計で見直すべき点を教えてください。