t0mmy

2020年12月30日に参加

学習履歴詳細

単体テストの考え方/使い方 11章 読了

やったこと

  • 単体テストの考え方/使い方 11章 単体テストのアンチパターン 読了

学んだこと

ポイント

  • プライベートメソッドをテストする際のアンチパターン
  • テストコードにドメイン知識が漏洩する状況
  • 具象クラスに対してテストダブルを作成する際の注意点

学び

  • プライベートメソッドは直接テストしない
    • 観測可能な振る舞いを通じて、間接的にテストする
  • 「具象クラスの一部分だけをモック化したい」という要求の元、具象クラスのモックを作成することはアンチパターン
    • 具象クラスが責務を負い過ぎている可能性が高い
  • テストのためだけにコードを変えてしまうと、プロダクションコードの検証ではなく、「テスト用にチューニングしたコードの検証」となってしまう
  • テストコードに、ドメイン知識を持ち込まない
    • ドメインロジックの中に移植すること

気づき

  • 「具象クラスを一部分だけモック化したい」と感じたら、単一責任の原則違反を疑おう
  • プライベートメソッドは直接テストしない
    • リフレクション等で頑張っても、労力に見合わず、壊れやすい
  • テストのためだけにコードを変えてしまうと、プロダクションコードの検証ではなく、「テスト用にチューニングしたコードの検証」となってしまう点
    • 目から鱗
  • 単体テストにて、Act のコードが多い場合、ドメイン知識の漏洩を疑ったほうがよさそう

メモ

プライベートなメソッドに対する単体テスト

プライベートメソッドをテストするために、プライベートメソッドを公開してはならない
- 「観察可能な振る舞いのみ検証する」という基本原則に反する
プライベートメソッドをテストする場合は、公開されている観察可能な振る舞いを通してテストすること。

観察可能なふるまいを可能な限り検証したにもかかわらず網羅率が低い場合、以下の問題が考えられる。
- デッドコード
- 絶対に通過しないコード(リファクタリングの際に消し忘れた...など)
- 削除するべき
- 抽象化の欠落
- 抽象化

テストのためだけに、private修飾子をpublicにしてしまう

アンチパターン。
テストするべきコードは、プロダクションコードと同じコード。
- テストのためだけにコードを変えてしまうと、プロダクションコードの検証ではなく、「テスト用にチューニングしたコードの検証」となってしまう

そもそも、公開するのは、観察可能な振る舞いのみ。
観察可能なふるまいを通じて、プライベートメソッドをテストする。

テストコードに、ドメイン知識が漏洩してしまう

テストのために、何らかのアルゴリズムなど、テストコードを動かすための何らかの知識を記述する必要があるような場合。
- プロダクションコードのアルゴリズムが変化すると、テストコードが壊れてしまう

これは、ドメイン知識がテストコードの中に漏洩していることを意味する。

◇対応策
- リファクタリングを通じて、ドメイン知識を含むロジックを、ドメインクラス側(≒観察可能な振る舞いの中)に閉じ込める
- テストコードに、「(ドメインロジックを走らせた結果である)期待する値」を直接書いてしまう

プロダクションコードの汚染

プロダクションコードの汚染とは
テストでのみ使用するコードを、プロダクションコードの中に実装してしまうこと

本番環境で動作するに際して、不要なコードが本番環境に持ち込まれることを意味する。
これは、以下二つの問題につながる。
- 不要なコードを実行することによるパフォーマンスの低下
- コード増加に伴う、可読性、および保守性の低下

テスト専用コードは、プロダクションコードに持ち込まないこと。

具象クラスに対するテストダブル

基本的に、テストダブルは、I/Fに対して作成する。
しかし、大多数のライブラリは、具象クラスに対してもテストダブルを作成できる。

具象クラスに対してテストダブルを作成すると、「プロダクションコードの内、一部分だけをモック化する」といったことが可能。

しかし、上記機能は使うべきではない。
- つまり、具象クラスに対してテストダブルを作成するべきではない

「具象クラスの一部分だけをモック化したい」という要望が生まれる場合、その具象クラスが責務を負い過ぎている(≒単一責任の原則に反している)可能性が高い。

◇対応策
単一責任の原則に則り、責務が一つになるよう具象クラスを分割すること。

単体テストにおける現在日時の扱い

現在日時を扱う独自クラスを構築し、環境(本番/テスト)に応じて動作を変化させる(いわゆる環境コンテキストパターン) -> アンチパターン
- コードが複雑になるだけ

◇対応策
現在日時情報を、明示的な依存として、メソッドに注入する。
- 可能な限り、値(または値オブジェクト)を注入する
- 値の注入が不可能であれば、「現在日時を返す」サービス(I/F)を注入し、現在日時が必要になったらサービスから受け取るようにする


まとめ

  • 単体テストのために、プライベートメソッドを公開してはならない
    • テストは、プロダクションコードと同じように取り扱ってこそ価値がある
      • テストだけ特別扱いしたら、それは「テスト用にチューニングしたコード」の検証になってしまい、プロダクションコードの検証にならない
    • 公開したメソッドが、テストの実装に結び付き、テストが壊れやすくなる
    • プライベートメソッドを使用するパブリックメソッドを通してテストすること
    • パブリックメソッドを通したテストが困難な状況は、抽象化の欠落が発生している可能性が高い
      • 要リファクタリング
  • プロダクションコードのアルゴリズムやロジックの知識を、テストに持ち込んではならない
    • 言い換えると、ブラックボックステストを行うこと
    • テストに持ち込まれる状況、というのは、ドメイン知識がテストに漏洩することを意味する
  • プロダクションコードの汚染とは、テストでのみ使用するコードを、プロダクションコードに持ち込むこと
    • プロダクションコードに不要なコード(≒処理)が増え、保守性とパフォーマンスの低下に繋がる
  • 「具象クラスの一部分だけをモック化したい」という要求の元、具象クラスのテストダブルを作成することはアンチパターン
    • 具象クラスが責務を負い過ぎている(≒単一責任の原則に反している)可能性が高い
  • 現在日時を環境コンテキストとして表現することは、プロダクションコードの汚染である
    • 現在日時は、依存として、明示的に注入すること
テスト

2023年05月03日(水)

2.0時間