ウェブエンジニア問題集
第1章

単体テストとは

7
この章の目次開く

単体テストは本番コードとは性質が異なり、目的なく書けば無駄になる。

この章では、まず「単体テストの定義」を整理します。

学習者学習者

テストは書いた方がいいって言われるけど、何を確認すれば「テストした」ことになるの?

テストの考え方を学ぶ人のイラスト

そもそも単体テストとは

単体テストとは、関数やクラスなどの小さな単位が、期待通りに動作するかを自動的に検証する仕組みだ。

  • 単体(ユニット 少量)のコードのテスト
  • 実行環境が隔離されている

複数のモジュールを組み合わせて確認する結合テストや、ブラウザ操作まで含めて確認するE2Eテストは、結合テストとE2Eテストの実践で扱っています。この章ではまず、単体テストが守る範囲に集中します。

先生先生

単体テストの「単体」は、必ずしも1つの関数だけを意味しません。大事なのは、検証したい振る舞いを小さく切り出せているかです。

単体テストで確認するもの、確認しないもの

単体テストで確認するべきなのは、コードの利用者から見える振る舞いです。

たとえば、料金計算の関数であれば「入力に対して正しい合計金額が返ること」を確認します。どの変数名を使っているか、内部で map を使ったか for を使ったかは、単体テストが守る対象ではありません。

確認するもの確認しないもの
戻り値が仕様通りか内部変数の値
状態が期待通り変わったかprivateメソッドの呼び出し順
外部APIやメール送信などの重要な呼び出し実装都合の関数分割
境界値や例外ケースの扱いカバレッジを上げるだけの呼び出し

何を単体テストにするべきか

すべてのコードに同じ濃さで単体テストを書く必要はありません。

優先度が高いのは、次のようなコードです。

  • 金額、権限、状態遷移など、間違えると影響が大きいロジック
  • 条件分岐が多く、手作業で確認しづらい処理
  • 仕様変更が入りやすく、今後も触る可能性が高い処理
  • バグが過去に出た、または出そうな境界値を含む処理

逆に、薄いラッパーや単純なプロパティ受け渡しだけのコードは、単体テストの優先度が低いことがあります。

PCで作業をしている女性のイラスト

単体テストの目的は、アプリケーションの継続的な開発において、コードの成長速度を落とさないこと

理由は、テストがなければリファクタリングや機能追加のたびに既存機能の動作保証が手作業になり、変更コストが時間とともに増大するからだ。

テストがあれば、変更の影響を即座に検知でき、安心してコードを変え続けられる。

もう一つ重要なのは仕様の明文化です。

テストコードは「この関数はこの入力に対してこの結果を返すべき」という仕様そのものになります。

コメントやドキュメントと違って実行可能なので、コードと乖離しません

各テストメソッドが「この実装コードは何を保証しているのか」を語るべきだ、というのはテスト設計の基本的な考え方です。

良いテストとは

テストコードはプロダクションコードと同様に、増えるほど保守コストが増大する。 テストは資産ではなく負債であり、価値のないテストは負債だけを積み上げる。

良いテストとは、コードで重要な部分が対象とされていて、最小限の保守コストで最大の価値を生み出すものだ。

テストの質とカバレッジは別物だ。

カバレッジが測るのは「その行が実行されたかどうか」だけであり、「正しい結果を検証しているか」は一切見ていない。

たとえば関数を呼ぶだけで戻り値が正しいかを確認しなくても、カバレッジは上がる。つまりカバレッジを上げること自体を目的にすると、通過するだけで何も守らないテストが量産される。

カバレッジは目標ではなく、テストされていない箇所を見つけるための参考指標として使うべきだ。

学習者学習者

じゃあ、カバレッジ80%みたいな目標は意味がないの?

先生先生

意味はあります。ただし「80%なら十分」ではなく、「残り20%に重要なロジックが残っていないか」を見るための入口として使うのが現実的です。

単体テストを読むときの判断基準

良い単体テストかどうかは、次の質問で確認できます。

  • 失敗したときに、どの仕様が壊れたか分かるか
  • 実装をリファクタリングしても、振る舞いが同じなら壊れないか
  • テスト名だけで、条件と期待結果が分かるか
  • テストデータが読みやすく、不要な値が混ざっていないか
  • 実行が速く、開発中に何度も回せるか

この章では単体テストの目的を整理しました。次章では、その目的をテストコードの形に落とし込むために、単体テストの構造を扱います。

ユニットテストクイズに挑戦するこの章で学んだユニットテストの知識を、4択クイズでアウトプットして定着させよう