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

実装の変更に振り回されないこと

5
この章の目次開く

「実装の内部構造を変えただけでテストが壊れる」状態は、リファクタリングを躊躇させるので、テストがあることが逆に開発の足枷になります。

学習者学習者

リファクタしただけでテストが赤くなる…。これってテストが悪いの?それともコードが悪いの?

テスト失敗に驚いている人のイラスト

テストの偽陽性とは

テストの偽陽性とは、テストが失敗したが、実際にはバグがないことを意味します。

偽陽性が少ないことは、リファクタリングの耐性を高めることにつながります。

逆に、偽陽性が多いテストは、失敗しても「またテストが壊れただけかもしれない」と思われるようになります。

テストの価値は、赤くなったときに開発者がすぐ反応できることです。失敗の信頼性が落ちると、テストは警報として機能しなくなります。

偽陽性でテストの意味がなくなってしまう

開発でコードを記述していて、問題あるコードを事前に検知できることがテストの意味ですが、偽陽性が多いと、その信頼性が失われてしまいます。

テストの偽陽性を減らす方法

最終的な結果を確認することで、偽陽性を減らすことができます。

テストが確認すべきなのは「外部から観測できるもの」です。これは大きく3つあります。

  • 戻り値
  • 状態の変化(DBに保存された、オブジェクトのプロパティが変わった等)
  • 外部への呼び出し

区別の基準はシンプルで、「その検証対象を変えたとき、ユーザーや外部システムが気づくか」です。

気づくなら振る舞い、気づかないなら実装の詳細です。

偽陽性を生みやすいテストの例

次のテストは、ユーザー名を正規化する処理を確認しているように見えます。

it('ユーザー名を正規化する', () => {
  const normalizer = vi.fn((name: string) => name.trim().toLowerCase());
  const service = new UserService(normalizer);
 
  service.createUser(' Alice ');
 
  expect(normalizer).toHaveBeenCalledWith(' Alice ');
});
typescript

このテストは、normalizer が呼ばれたことを確認しています。

しかし、ユーザーから見て重要なのは「保存されたユーザー名が alice になっていること」です。内部で normalizer を呼ぶかどうかは実装の詳細です。

次のように、観測できる結果を見る方がリファクタリングに強くなります。

it('ユーザー名の前後の空白を取り除き、小文字で保存する', () => {
  const repository = new InMemoryUserRepository();
  const service = new UserService(repository);
 
  service.createUser(' Alice ');
 
  expect(repository.findByName('alice')).toEqual({
    name: 'alice',
  });
});
typescript

このテストなら、内部で trimtoLowerCase を直接呼んでも、別の関数に切り出しても、振る舞いが同じなら壊れません。

先生先生

テストが実装の「手順」を監視しすぎると、リファクタリングの自由度が下がります。できるだけ「結果」を見ましょう。

実装の詳細に依存しているサイン

次のようなテストは、偽陽性が増えやすい傾向があります。

  • privateメソッドや内部関数の呼び出しを検証している
  • メソッドの呼び出し順を細かく検証している
  • テスト対象の内部データ構造を直接見ている
  • テスト名が「何を保証するか」ではなく「どのメソッドを呼ぶか」になっている
  • リファクタリングだけで大量のテストが失敗する

もちろん、外部APIの呼び出しやメール送信など、呼び出し自体が振る舞いになるケースもあります。

大事なのは、「その呼び出しを変えたら、ユーザーや外部システムが困るか」を考えることです。

リファクタリング耐性を高める書き方

リファクタリング耐性を高めるには、次の順で検証方法を選びます。

  1. 戻り値で確認できるなら、戻り値を検証する
  2. 戻り値がないなら、状態の変化を検証する
  3. 外部への通知や保存が本質なら、呼び出しを検証する

この優先順位は、後の章で扱う「出力値ベース」「状態変化ベース」「コミュニケーションベース」の違いにもつながります。

偽陽性を減らすことは、単にテストを壊れにくくする話ではありません。

安心してリファクタリングできる状態を作り、アプリケーションを長く変更し続けるための土台になります。

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