はじめに
この記事は、「単体テストの考え方/使い方」という本を読んだときのメモです。
まだ読書途中なので、随時加筆修正していきます。
ようやく読了しました〜🎉
この本を読みたいと思ったきっかけ
日々TDDをしながらアプリ開発をしていく中で、「実装コードを書くためにテストが必要だからテストを書く」ことに対して、何かよくわからないけどもやもやした気持ちになることが増えた。
このテストについてもっと本質的なことを知りたくなった。
そんなとき、「単体テストの考え方/使い方」という本に出会った。
少し読んでみた感想
今まで点でしかなかった知識が線でつながる感覚にワクワクが止まらない!
技術書なのにどんどん読み進められちゃうほど面白い!!
読書メモ
第1部 単体(unit)テストとは
第1章 なぜ、単体(unit)テストを行うのか?
- ソフトウェア開発プロジェクトの成長を「持続可能」なものにするため
- テストの質が悪いと
- テストが間違った理由で失敗することが頻繁に起こる
- バグを検出できない
- テストに時間がかかりすぎて簡単に保守できなくなる
- 作成する単体テストの価値とその維持にかかるコストの両方を考慮する必要がある
- プロダクションコードのリファクタリングに伴って、テストコードをリファクタリングする
- プロダクションコードを変更するたびにテストを実行する
- テストが間違って失敗したとき、その対処をする
- プロダクションコードがどのように振る舞うのかを理解するために、テストコードを読む
- 網羅率(coverage)はテストコードの質が悪いことを判断するには効果があるが、テストコードの質が良いことを判断することには向かない
- 何がテストコードの質を良くするのか?
- テストすることが開発サイクルの中に組み込まれている
- コードベースの特に重要な部分のみがテスト対象となっている
- 最小限の保守コストで最大限の価値を生み出すようになっている
(感じたこと)
テストの質とかあんまり考えたことなかったけど、テストにも質があるのか💡
テストコードのリファクタリングってどうしたらいいかまだよくわかってない・・・
第2章 単体テストとは何か?
- 単体テストの定義
- 「単体(unit)」と呼ばれる少量のコードを検証する
- 実行時間が短い
- 隔離された状態で実行される
- 「隔離」の捉え方の違いから、「古典学派(デトロイト学派)」「ロンドン学派」が自然発生した
- 著者は古典学派を好んでいる
- ロンドン学派が考える隔離
- テスト対象システム(System Under Test)から協力者オブジェクト(collaborator)を隔離すること
- テスト対象のクラスが他のクラスに依存しているのであれば、その依存をすべてテストダブルに置き換えなくてはならない
- 古典学派が考える隔離
- テストケース同士を隔離すること
- 各テストケースがお互いに影響を与えることなく個別に実行できなくてはならない
- ロンドン学派が考える単体
- 1単位のコード(クラス)
- 古典学派が考える単体
- 1単位の振る舞い
- ロンドン学派の課題
- 検証内容が詳細になりすぎてしまう
-> 単体テストがテスト対象の内部的なコードと密接に結びついてしまう - 一部のコードを簡単にテストできないとき、コードの設計に問題があることを強く示唆しているが、テストダブルを使ってテストができるようにしても根本的な問題は解決しない
- 検証内容が詳細になりすぎてしまう
(感じたこと)
単体テストの定義ってあったのね。知らずに書いてたよ・・・実行時間とか気にしたことなかったし・・・
ロンドン派とデトロイト派があったのは知ってたけど、「隔離」に対する考え方の違いで自然発生したとは知らなかった〜!
今までロンドン派でテストを書いてると思ってたけど、テストするクラスも隔離してるけど、テスト同士も隔離するようにしてるから、いいとこどりしながらテストを書いてたのかな・・・?
著者がデトロイト派なだけに、ロンドン派の課題が言語化されていて、その辺りも気にしながらテストを書こうって思えるようになったのは収穫かも。
第3章 単体テストの構造的解析
- AAA パターン
- 準備(Arrange)
- 実行(Act)
- 確認(Assert)
- AAA パターン ≒ Given-When-Then パターン
- TDD を取り入れている場合、確認フェーズから書き始めることがある
- 開発する機能の振る舞いをまだ十分に把握していないことがある
- その機能がどう振る舞うべきなのかを最初に考え、次にどう実現させるかを考えると考えやすい
- 単体テストで回避すべきこと
- ひとつのテストケースの中に複数の同じフェーズがある(Given-When-Then-When-Then-When-Then)
- ひとつのテストケースの中で複数の振る舞いを検証している -> 統合テストに属する
- テストの実行時間が長くなる -> 単体テストの定義、実行時間が短いに反する
- テストを理解するのに時間がかかる -> 単体テストのテストケースは簡潔で簡単に理解できるものであること
- テストケースを分割して、ひとつの実行フェーズとひとつの確認フェーズを持つテストケースを複数作成する
- if 文の使用
- 単体テスト、統合テストに関わらず、分岐のない単純な流れにしなくてはならない
- 分岐がある -> 複数のことを検証しようとしている
- テストが読みづらくなる -> 理解しづらいものになる -> テストに対して余分な保守コストがかかるようになる
- ひとつのテストケースの中に複数の同じフェーズがある(Given-When-Then-When-Then-When-Then)
- 準備フェーズが、実行フェーズと確認フェーズを合わせたサイズよりはるかに大きくなる場合
-> その一部を同じテストクラスのプライベートメソッドに切り出す or 別のファクトリクラスを作る - 実行フェーズが1行を超す場合は注意が必要
-> APIがきちんと設計されていないことを示唆している - 確認フェーズが大きくなり過ぎる場合
-> プロダクションコードでの抽象化がうまくいっていない可能性が高い - テストケースから各フェーズを示すコメントを取り除くか否か
- 3つのフェーズが明確に区別しやすくなっていることが重要
- 各フェーズ内のコードを書くのに空白行が不要で、各フェーズを空白行で区切れるのであればコメントを取り除いてOK
- ひとつのフェーズで空白行を用い処理をまとめる場合、3つのフェーズが不明確になるため、各フェーズを示すコメントは残す
- 複数のテストケースで同じコードを共有する場合
- 複数のテストケースで準備フェーズのコードが同じでも beforeEach に切り出さない
- 各テストケースが読みづらくなる
- テストケース間の結びつきが強くなってしまう -> ひとつのテストケースに関する修正が他のテストケースに影響を与えてはいけない
- 複数のテストケースで準備フェーズのコードが同じでも beforeEach に切り出さない
- テストタイトルを付けるとき
- 厳格な命名規則に縛られないようにする
- 問題領域のことに精通している非開発者に対してどのような検証をするのかが伝わるような名前をつける
- テスト対象のメソッド名を含めない
- リファクタリングでメソッド名を変更したとき、テストタイトルも修正する必要がある -> テストに対して余分な保守コストがかかるようになる
- パラメータ化テスト
- メリット:テストコードの量を劇的に減らせるようになる
- デメリット:このテストが何の事実を表現しているのか分かりづらくなる
(感じたこと)
Given-When-Then-When-Then-When-Then・・・
これ、やってるなぁ〜。
確かに何が検証したいのか、ぱっと見て分からない。
// Given
// When
// Then
このコメントは残してもいいやつだったんだ!!
無理して消してたけど、これからは残しておこう📝
ちょっとずつ、テストの質ってやつが分かってきた気がする。
多分、今開発してるプロダクトのテスト・・・スメってるカモ🦆
気づいたら Given が beforeEach にまとまってて、各テストケースから Given が消えてたし😅
この本読んでなかったら、私も同じことやってたかもな〜・・・テストのリファクタリングって難しい。。。
パラメータ化テストは知らなかった。三角測量に使うとよさそう!
第2部 単体テストとその価値
第4章 良い単体テストを構成する4本の柱
- 4本の柱
- 退行(regression, バグ)に対する保護
- リファクタリングへの耐性
- 迅速なフィードバック
- 保守のしやすさ
- 退行(バグ)に対する保護 - 気に掛けるポイント -
- テストの際にできるだけ多くのプロダクションコードを実行させる
- 複雑なビジネスロジックが記述されたコード
- ビジネス的に重要な機能
- 使用しているライブラリやフレームワーク
- 偽陰性:テストが成功したにも関わらず、検証された機能に欠陥がある(退行に対する保護)
- 偽陽性:意図した振る舞いであるにも関わらず、テストが失敗する(リファクタリングへの耐性)
- 真陰性:テストが成功し、テスト対象も正しい振る舞いをしている
- 真陽性:テストが失敗し、テスト対象の機能も間違った振る舞いをしている
- リファクタリングへの耐性
- テストが失敗することなく、どのくらいプロダクションコードのリファクタリングを行えるか
- リファクタリングを行っても、偽陽性が生まれづらい性質のこと
- 偽陽性が起きる要因
- テストコードがプロダクションコードに密接に結びついている
- 退行に対する保護とリファクタリングへの耐性を備えることはテストスイートの正確性を最大限に引き上げることになる
- 迅速なフィードバック
- テストの実行時間が短い = フィードバックを得てから改善するまでの時間が短くなる
- 保守のしやすさ
- テストケースの理解のしやすさ
- テストケースのサイズが小さいほど、テストコードは読みやすい → 変更もしやすい
- テストコードの品質はプロダクションコードの品質と同じくらい重要
- テストを実施する難しさ
- テスト時に使われるプロセス外依存が少ないほど、テストの実施は簡単になる
- テストケースの理解のしやすさ
- 4本の柱すべてを最大限に備えることは不可能
- 退行に対する保護とリファクタリングへの耐性と迅速なフィードバックは互いに排反する性質を持っている
- 最善の単体テストとは?
- リファクタリングの耐性と保守のしやすさを最大限に備え、退行に対する保護と迅速なフィードバックの間でバランスをとる
- テストスイートを堅牢にするために最も優先すべきこと
- 偽陽性を取り除くこと
(感じたこと)
テストを書く理由として、既存の機能が失われていないかを確認できるので、自信を持ってリファクタリングや新しい機能を追加できるから。というのが今までの認識だった。間違ってはいないと思うけど、これだけじゃ要素としては足りなくて、ただテストを書けばよいのではなく、「偽陽性が生まれづらい」テストを書くからこそ、自信を持ってリファクタリングや新しい機能を追加できるのかなと思った。
今のところグリーンになるまで次のコードを書かないことは徹底しているので、レッドの状態を放置していないことはよい状態かなと思う。しかし、ハッピーケースのテストが多く、偽陰性が潜んでいる可能性は否定できない。
第5章 モックの利用とテストの壊れやすさ
- テストダブルは大きく2つに分けることができる
- モック(モック、スパイ)
- スタブ(スタブ、ダミー、フェイク)
- モック(モック、スパイ):テスト対象システムから外部に向かうコミュニケーション(出力)を模倣し、検証するのに使う
- スタブ(スタブ、ダミー、フェイク):テスト対象システムの内部に向かうコミュニケーション(入力)を模倣する(検証には使わない)
- モックとスパイの違い
- モック:モックフレームワークの助けを借りて生成
- スパイ:開発者自身の手で実装される(手書きのモック)
- スタブとダミーとフェイクの違い
- ダミー:null 値や一時しのぎで使われる文字列などのシンプルなハードコーディングされた値
- スタブ:設定によって返す結果を異なるシナリオごとに変えられる完全に自立した依存として振る舞う
- フェイク:まだ存在しない依存を置き換えるために作成される
- スタブとのやり取りを検証するのはアンチパターン
- スタブはテスト対象システムが最終的な結果を生成するのに必要なデータを提供するだけで一過程にすぎない
- 実装の詳細と結びつきやすくテストが壊れやすくなる
- 過剰検証:最終的な結果の一部とはならないものを検証すること
- コマンド・クエリ分離の原則
- コマンド:副作用あり、戻り値なし・・・モック
- クエリ:副作用なし、戻り値あり・・・スタブ
- 一部例外はあるが、可能な限り従う
- すべてのプロダクションコードは2つの観点で分類できる
- 公開されたAPI or プライベートなAPI
- 観察可能な振る舞い or 実装の詳細
- 観察可能な振る舞い(必ずどちらかに該当する)どちらにも該当しないものが実装の詳細
- クライアントが目標を達成するために使う公開された操作
- クライアントが目標を達成するために使う公開された状態
- 理想は、システムが公開しているAPIが観察可能な振る舞いと一致し、そのシステムのすべての実装の詳細がクライアントから完全に隠れるようになっていること → APIをきちんと設計する必要がある
- カプセル化:不変条件の侵害からコードを守る手段
- 実装の詳細を隠すこと
- データを操作させるのにメソッドを経由させること
- テストケースを作成する際、どのようなビジネス要求があるのかをそのテストケースから分かるようにしなくてはならない
- アプリケーションが行うコミュニケーションには2種類ある
- システム内コミュニケーション → 実装の詳細
- システム間コミュニケーション → 観察可能な振る舞いの一部
- システム内コミュニケーションの確認にモックを使うことはテストを壊れやすくすることにつながる
(感じたこと)
テストダブルが大きく2つに分かれることは知らなかったけど、言われてみればそうかもな〜って感じがした。だけど、ダミーとスタブの違いは教えてもらったものとちょっとニュアンスが違うように感じた。スタブは固定値を返すもので、ダミーはコンパイルを通すためだけに使い、もしダミーが使われたときはエラーを返すイメージだった。
コマンド・クエリ分離の原則って初めて聞いたけど、確かにひとつの関数に副作用もあって戻り値もあったらややこしいかも。
スタブとのやり取りを検証するのはアンチパターンってあるけど、ロンドン派でテスト書いてるとあるあるな気がしなくもない・・・?気のせいかな・・・。
コードのカプセル化って意識して書いてないから、できてない部分が結構あるんじゃないかとふと思ってしまった。private とか public を意識的に書いた記憶がほとんどない・・・。
第6章 単体テストの3つの手法
- 単体テストの3つの手法
- 出力値ベーステスト(戻り値を確認) プロダクションコードが副作用のないコードであること
- 状態ベーステスト(状態を確認) 副作用で変化した状態を確認
- コミュニケーションベーステスト(オブジェクト間のやり取りを確認) 協力者オブジェクトをモックに置き換える
- 2つの学派の好み順
- 古典学派:出力値ベーステスト → 状態ベーステスト → コミュニケーションベーステスト
- ロンドン学派:出力値ベーステスト → コミュニケーションベーステスト → 状態ベーステスト
- リファクタリングへの耐性を維持するのに必要なコスト
- 出力値ベーステスト:低い
- 状態ベーステスト:普通
- コミュニケーションベーステスト:普通
- 保守のしやすさを維持するのに必要なコスト
- 出力値ベーステスト:低い
- 状態ベーステスト:普通
- コミュニケーションベーステスト:高い
- 関数型プログラミングの目標
- ビジネスロジックを扱うコードと副作用を起こすコードを分離すること
- 関数型アーキテクチャでは、副作用をビジネスオペレーションの最初と最後に持っていくことで、ビジネスロジックと副作用を分離しやすくしている
- ビジネスロジック:決定を下すコード、関数的核、不変核
- 副作用:決定に基づくアクションを実行するコード、可変殻
(感じたこと)
ビジネスロジックを扱うコードと副作用を起こすコードを分離することってあんまり意識したことないけど、コンポーネント(コントローラ)、サービス、リポジトリみたいな感じに分けて書くことがそういうことなのかな??この章話が難しかった〜。
単一の責務、疎結合・・・キーワードは分かるけど、いざコードにしようとすると、どう分ければいいのか?どうすれば疎結合になるのか??ピンともこないや・・・。
関数型プログラミングとか関数型アーキテクチャについてもう少し理解を深めたいな〜って気持ちになりました。ここら辺をもう少し意識的に書けるようになると、テストの質も上がるかも??
第7章 単体テストの価値を高めるリファクタリング
- プロダクションコードを分類する視点
- コードの複雑さ、もしくは、ドメインにおける重要性
- 協力者オブジェクトの数
- プロダクションコードは4種類に分類できる
- ドメイン・モデル / アルゴリズム
- 取るに足らないコード
- コントローラ
- 過度に複雑なコード
- 過度に複雑なコード
- テストをすることが難しい
- テスト対象のコードがフレームワークとなる依存(非同期、複数スレッドでの実行、ユーザインターフェイス、プロセス外依存とのコミュニケーションなど)に直接結びついている
- コードを分割する
- 質素なオブジェクトと呼ばれる設計パターンを導入する
- ロジックをほぼ(全く)含まず、テストすることが難しい依存を結びつけた、質素なクラスを作成する
- コントローラは依存の数が多くても、複雑さはもってはならない
- ドメインクラスは複雑になっても、依存の数はできる限り少なくなるようにする
- Active Record パターン
- ドメインクラスがデータベースから自身のデータを取得したり保存したりできるようにする設計パターン
- シンプルで運用される期間が短いプロジェクトだとうまく機能する
- コードベースが大きくなるにつれ、うまく対応できなくなる傾向にある
- ビジネスロジックとプロセス外依存とのコミュニケーションを分離できていない
- ドメインクラスからプロセス外依存とのコミュニケーションを取り除く
- 「尋ねるな、命じよ(Tell, Don't Ask)」
- データとそのデータに関連する操作をまとめておき、その操作を使ってデータの処理をさせる、という原則
- 「尋ねるな、命じよ」という原則をクライアント(メソッドを使う側)に遵守させる作りにする
- 事前条件がドメインにおいて重要であれば、その条件はテストされるべき。そうでなければテストしない。
- 抽象化する対象をテストするより、抽象化された結果をテストするほうが簡単。
(感じたこと)
「尋ねるな、命じよ(Tell, Don't Ask)」ってなんかかっこいいよね(笑)映画のワンシーンにありそう(笑)
この章は読んでても難しかった〜。
書き留めれていないことがいっぱいあるけど、いつかもう一度読んだときにこのメモをブラッシュアップできたらいいな。
第3部 統合(integration)テスト
第8章 なぜ、統合(integration)テストを行うのか?
- 統合テストとは、単体テストの条件をすべて満たせないテストのこと
- システムがプロセス外依存と統合した状態で意図したように機能するのかを検証する
- 単体テストはドメインモデルを検証し、統合テストはドメインモデルとプロセス外依存とを結びつけるコードを検証する
- 単体テストと統合テストのテストケースの数をうまく調整することは、プロジェクトを継続的に運用する上で重要なことの一つである
- 単体テストより統合テストの保守コストは高くなる
- プロセス外依存を利用可能な状態に維持する必要があるため
- 協力者オブジェクトの数が増えるにつれ、テストケースのコード量が増えるため
- 統合テストは単体テストより
- 優れた退行に対する保護が備わる
- リファクタリングへの耐性も備わる
- 統合テストはビジネスシナリオごとに1件のハッピーパスと単体テストでは検証できないすべての異常ケースを検証することが適切
- 検証する内容のほとんどを単体テストに持たせることで、テストスイート全体の保守コストを少なくし、それと同時にビジネスシナリオごとに1件か2件の包括的な統合テストを行うことで、システム全体が正しく機能することに自信が持てるようになる
- 非常に単純なアプリケーションでも、統合テストの価値が下がることはない
- コードがいかに単純であったとしても、他のシステムと統合した状態で意図したように機能するのかを検証することは重要である
- 早期失敗(Fail Fast)
- 望んでいないエラーが起こったとき、すぐにその処理を停止させるという原則
- 統合テストの代わりに使える選択肢のひとつ
- フィードバックループの短縮ができる
- 保存される状態の保護ができる
- プロセス外依存は2つに分類できる
- 管理下にある依存
- テスト対象のアプリとその依存とのコミュニケーションは外部から見ることはできない
- テスト対象のアプリしかアクセスしないデータベースなど
- 管理下にない依存
- 管理下にない依存とのコミュニケーションは外部から見ることができる
- メールサービス、メッセージバスなど、他のアプリに見える副作用を発生させる
- 管理下にある依存
- 管理下にある依存とのコミュニケーションは実装の詳細、統合テストでは実際のインスタンスを使う
- 管理下にない依存とのコミュニケーションは観察可能な振る舞い、統合テストではモックを使う
- 管理下にある依存と管理下にない依存の両方の性質を持つプロセス外依存
- データベースに他のアプリと共有されるテーブルと共有されないテーブルが存在する場合
- 他のアプリと共有されるテーブルはモックに置き換え、共有されないテーブルは管理下にある依存として扱う
- 統合テストで実際のデータベースを使えない場合、ドメインモデルの単体テストを作成することだけに専念する
- 最長のハッピーパスとは、すべてのプロセス外依存を経由してビジネスシナリオを正常に終わらせる実行経路のこと
- インターフェイスの実装クラスが1つしかないのであれば、そのインターフェイスは抽象ではない
- 本来 抽象化 とは、発見する ことであり、作り出す ことではない
- 現時点において必要ではない機能に対して時間を費やすべきではない(YAGNI(You Aren't Gonna Need It)原則)
- 将来的に追加される機能を予測して開発したり、既存のコードを修正したりすべきではない
- 統合テストのベストプラクティス
- ドメインモデルの境界を明確にする
- アプリケーションを構成する層を減らす
- 循環依存(適切に機能させるために2つ以上のクラスが直接的もしくは間接的にお互いに依存する状態)を取り除く
- 1つのテストケースの中に実行フェーズを複数持つことが妥当なのは、プロセス外依存に何らかの制限があって開発者がテストのためにそのプロセス外依存を好きなようにすることができない場合だけである
- ログ出力はテストするべき?
- ユーザ、クライアント、非開発者からも見られることを意図している場合、テストは必要(サポートログ)
- 開発者しか見ないのであれば、テストは不要(診断ログ)
(感じたこと)
正直まだ統合テストやE2Eテストの経験がないからなんとも・・・。
だけど、モックをどういう時に使うのかなど、考え方は単体テストと重なる部分が多い印象。
開発中のプロダクトを思い浮かべると、YAGNI原則に反しているような気がしなくもない。今の機能では state はまだリフトアップする意味ないのにしてたりする。機能を勝手に追加してるわけじゃないけど、先を見越した実装になりがちなのが気になる。
あとはエラーハンドリングをあんまりしていないので早期失敗しない可能性を秘めている気がする・・・。
同じチームの PM は非開発者になるので、ログがほしいって言われたら、ログに対してテストが必要ってことなのか🧐
第9章 モックのベスト・プラクティス
- モックは、アプリケーションの境界に位置する管理下にない依存が行うコミュニケーションを検証するときだけにする
- 外部との境界にもっとも近いコンポーネントをモックに置き換えるようにする
- 外部システムとのやり取りにおいて、後方互換の保証がテストに対して求められることであり、モックを導入する目的である
- モックはライブラリを使うより自前で用意するスパイ(手書きのモック)の方が優れている
- 実行結果を確認するコードを記述できる
- そのコードを様々なテストケースから利用できるようになり、テストケースのコード量が減る
- テストで確認をする際はプロダクションコードを信頼すべきではない
- 特定のメソッドが呼び出されたことしか検証していない場合、プロダクションコードを過度に信頼していることになる
- モックの利用は統合テストに限定する(単体テストでは使わない)
- モックの呼び出し回数を常に確認する
- 想定する呼び出しが行われていること
- 想定しない呼び出しが行われていないこと
- モックの対象になる型は自身のプロジェクトが所有する型のみにする
- 管理下にない依存へのアクセスにライブラリをしている場合、ライブラリを内包する独自のアダプタを作成し、そのライブラリが提供するものではなく、自身で作成したアダプタに対してモックを作る
(感じたこと)
スパイのニュアンスが私の知ってるのとやっぱりなんだか違う・・・。
私の認識では、テスト・ダブルのスパイは、呼ばれた回数や引数を確認するもので、モックは結果の確認をする機能まで含んだものって感じです。
単体テストでモックを使わないってあたりは、デトロイト派の見解だな〜と感じる。
モックの呼び出し回数は、1回呼び出されていることを確認するのと、1回しか呼ばれていないことを確認するのでは、なんかニュアンスが違うよね。前者は複数回呼ばれていても1回でも呼ばれていれば OK な感じがする。後者は2回も3回も呼ばれているのは NG。呼び出し回数を正確に確認するのは大事そう。
ライブラリを内包する独自のアダプタを作成するやつは、なんかやってた気がする・・・。必要なところだけ持ってきて自前で準備して使ってたような(遠い目・・・)。
第10章 データベースに対するテスト
- データベースをテストするのに必要な事前準備
- スキーマを Git などで管理する
- 開発者ごとに個別のデータベースインスタンスを用意する
- データベースに対する変更を本番環境に反映する際は移行ベースを用いる
- データベース操作とトランザクションの分離
- ビジネスオペレーションが終わったときに、そのオペレーション中に起こった全てのデータの変更をデータベースに反映するか否かを決定するだけの責務 と、実際にデータベースのデータを変更するだけの責務 に分ける
- リポジトリ、トランザクション と呼ばれるクラスに分けることで実現できる
- 統合テストは、テストケースを1つずつ実行させる
- テストケースの実行前に、データの後始末を行う
- リポジトリを直接テストすると保守コストを非常に高くしてしまうため、統合テストのシナリオの一部に含ませ間接的に検証する
(感じたこと)
スキーマは Git で管理できてる。
データベースに対するテストってやったことあったっけ??
最近バックエンドを触ってなさすぎて記憶が随分遠くに・・・。
この本を読んでると、フロントエンドからバックエンドまで単体テストをしながら実装して、統合テストや E2E テストもやってみたくなりました。
第4部 単体テストのアンチ・パターン
第11章 単体テストのアンチ・パターン
- プラーベートなメソッドは直接テストすべきでない
- プライベートなメソッドを観察可能な振る舞いの一部に含め検証する
- 抽象化の欠落とは、重要なビジネスロジックがプライベートなメソッドに埋もれてしまうこと
- 抽象化を行い、重要なビジネスロジックを別のクラスとして抽出しテストする(メソッドを公開しない)
- 単体テストを行えるようにすることだけを目的にプライベートな状態を公開してはならない
- テストでは本番環境とまったく同じ方法でテスト対象のコードとやりとりしなくてはならない
- テストのためにプライベートなAPIを公開してはならない
- プロダクションコードに定義された特定のロジックやアルゴリズムを、テストコードで使わないこと
- テストでのみ必要なコードをプロダクションコードに加えない(プロダクションコードへの汚染)
- 既存の機能をそのまま使えるようにするために、具象クラスをテストダブルの対象にしないこと(具象クラスが単一責任の原則を遵守していない可能性がある)
- 環境コンテキストから現在日時を扱うのはアンチパターン
- 現在日時を依存として明示的に注入するようにする(可能な限り現在日時を値として注入する)
(感じたこと)
観察可能な振る舞いか〜。デトロイト派 vs ロンドン派 って感じが否めない。
テストを書く時は、できるだけインプットとアウトプットを意識して、実装に自由度を持たせるようなテストの書き方をするようにしてるけど、ロンドン派は実装の詳細と結びつきやすいテストになってしまうことが少なくない気がする。前に何チームかに分かれて TDD をしながらじゃんけんプログラムを書いたことがあるけど、結果は同じなのにアプローチはみんな違ってて面白かったな〜☆
単一責任の原則って頭では理解してるつもりだけど、実際の開発コードの中で実践できてるかな〜?最近テストコードが1ファイル 5000行 を超えてて、そのファイルの全体テストを回すと1分以上かかる。これって本当に大丈夫なの??って気にはなってるが、正常か異常なのかを判断できるスキルをまだ持ち合わせていない・・・。
おわりに
プロダクションコードを良くするためにはどうするか?みたいな本って、コードの設計からアプローチして書いているものが多いように感じる中で、この本はテストが先にあって、プロダクションコードをどうリファクタリングするとよいのかという視点で書かれているので、TDD 開発をしている人には読みやすい本だと思った。
おすすめの読み方は、各章のおわりに載っている「まとめ」を先に読む。そして、そこに行き着いた理由はなんだろう?と疑問をもって中身を読むと面白い☆
私自身 TDD 開発をしているので、この本で得た気づきを日々のプラクティスで体現していきたい。特に第4章くらいまでは読みやすいし気づきも多かったように感じるので、また時間を作って読みたいなと思います。