はじめに
テスト駆動開発(TDD)で学んだことをまとめます。
本記事では、こちらの書籍の第1部から学んだ内容となります。
検証環境
言語:Java11
TDDで目指すこと
TDDでは動作する綺麗なコードを作り続けることを目指します。
どうやって
TDDでは、まずテストコードを書くことから始めます。
次に書いたテストコードで検証しつつ、「動作する」プロダクトコードを書きます。
プロダクトコードが動作すれば、同じくテストコードで検証しながら「綺麗な」プロダクトコードに変えていきます。
具体的な行動サイクル
TDDでは、以下の5ステップに取り組みます。
1.まずはテストを1つ書く
2.全てのテストを実行し、新しいテストの「失敗」を確認する
3.テストを成功させる(完全に要件を満たさなくて良い)
4.全てのテストを再実行し、成功することを確認する
5.リファクタリングで重複を除去する
TDDに取り組む上で最低限のルール
TDDには、最低限守るべきルールが2つあります。
それは
・プロダクトコードを書く前に、失敗するテストコードを必ず書く
・重複を除去する
です。
TDDの行動サイクルを深堀り
0.TODOリストを書く(5ステップの補助動作)
5ステップに入る前に予備動作です。
まず、TODOリストを書きましょう。
書籍でもTODOリストを作りながら取り組むことが推奨されています。
TODOリストを作成することでやるべきことを忘れず、目の前のことに集中できるからです。
つまり、TODOリストをTDDに取り組むための行動座標にするということです。
まずは実現したい機能について箇条書きで書き出します。
TDDではテストコードを書くところから始めます。
そのため、今すぐにでもテストコードが書けるレベルまで実現したい機能を分解した内容を記載します。
TODOリストを書き出したら、着手できそうなものから順に1つずつ取り出して着手します。
また、TODOリストに記載した内容に着手する過程で発見、遭遇した課題があれば、その都度TODOリストに追記します。
1.まずはテストを1つ書く
TDDではテストを書くことから始めます。
TODOリストから着手しやすいものからで良いので、1つ選択してテストコードを書きます。
※要件に必要なクラスなどのプロダクトコードは後回しです。
インターフェースを考える
テストを書く時、実現したい機能を使用する時のインターフェースから考えます。
作りたい機能のインターフェースを先に定義することで、使う側の視点で設計方針を検討することが出来ます。
機能を使用したテストコードを書く
インターフェースを決めたら、その機能を使用したテストコードを書きます。
機能を実装せず、テストコードを先に書いているため、この時点でテストが通らなくてもOKです。
(そもそもテスト対象自体がなければ、コンパイルエラー等が発生しているレベルだと思います。)
2.全てのテストを実行し、新しいテストの「失敗」を確認する
テストコードが書けたら、テストを実行して「失敗」することを確認します。
テストだけに注力している段階なので、コンパイルエラー等、失敗の原因が目に見えていたり、気になるところが点在していると思います。
テストを成功させるために必要なことはTODOリストに追記していきます。
3.テストを成功させる(完全に要件を満たしてなくて良い)
次に「失敗」しているテストを「成功」するところまで持っていきます。
テストを実行できるようにする
追記したTODOリストを基にプロダクトコードを実装します。
この時、システムの要件を完全に満たす必要はありません。
あくまで目の前のテストを通すためだけを考え、コンパイルエラー等をなくし、テストを実行できるようにします。
テストが成功するように修正する
次に最小限の修正でテストが成功するところまで実装します。
テストを通すだけなので、クラスやメソッドの中身は空で良いですし、
メソッドから何か返す必要があったとしても、テストを成功させるために必要な値を固定で返すくらいで良いです。
プロダクトコードとしての正しさやコードの美しさは後から追求します。
大切なことは小さいステップを踏むことです。
4.全てのテストを再実行し、成功することを確認する
テストを通すだけの実装が出来たら、テストコードを再実行して「成功」を確認します。
ここまでの過程で、「成功するテスト」と「完全には要件を満たしていない機能」が出来ました。
このままでは完全には要件を満たしていなかったり、コードも汚いままです。
次のステップでリファクタリングを繰り返し、改善に取り組みます。
5.リファクタリングで重複を除去する
プロダクトコードの実装はここからが本番です。
要件を満たし、読みやすいような綺麗なプロダクトコードにリファクタリングしていきます。
そしてプロダクトコードを修正する度、「成功するテスト」を実行して動作に狂いが生じていないか確認します。
「成功するテスト」を維持してリファクタリングが完了すれば5つのステップが完了です。
ステップ1に戻ってTODOリストから新しい課題に取り組みます。
2周目以降では、これまで作成してきたテストは全て実行してプロダクトコードに不備が出てないことを都度確認していきます。
リファクタリングの観点
依存性と重複
テストコードとプロダクトコードを書いているとお互いに重複したコードが見受けられるようになります。
しかし、問題は重複ではなく、「プロダクトコードを変えたらテストコードも変えなければならない」といったような依存関係にあります。
重複
ほとんどの場合、重複は複数の箇所に同じようなコードが出てくる形だと思います。
オブジェクトを使えば抽象化によってロジックの重複をきれいに取り除けます。
また、重複を取り除くと、依存性も取り除くことに繋がります。
※これがTDDの第2のルールが存在している理由でもあります
次のテストに行く前に重複を除去することで、次のテストを最小限の修正で成功させれるようになります。
とても重要!!
ステップ5がなければ、最初の4つのステップは無意味になる
各フェーズには、それぞれの目的と解決法があり、優先度も異なります。
最初の3つのステップは、なるべく速く通過し、新しい機能の状態を確認できるところまで行くことを優先しましょう。
いかに速く、テストで確認しながらリファクタリング出来る状況まで行けるかが重要です。
そして「動く綺麗なコード」にする時間を多く取りましょう。
テストコードで意識すべきことと得られるメリット
テストを書くことは、開発者のためだけではありません。
開発を通して考えたこと、発見したことをメンバーや未来の自分に伝える役割もあります。
場合によっては、システムに要求を出す人と開発者とのコミュニケーションを加速させる役割にもなります。
そのため、テストは読み手に負荷を与えないようにすることも大切です。
デメリットを理解する(コード量と時間)
TDDを実施すると、テストコードとプロダクトコードの両方を書く必要があります。
TDDを真摯に取り組むと、テストコードとプロダクトコードの行数は同じくらいになります。
そのため、TDDのメリットを享受するには
・2倍のコードを書く
・半分のコード量で同じだけの機能を書く
のどちらかが必要となります。
TDDを実業務に取り入れる際は、上記2点を考慮する必要があります。
TDDで書いたテストの品質
TDDで作成されるテストは、システムを開発し続けるためのもとしては有用です。
ただし、以下のテストの代わりにはなりません。
・パフォーマンステスト
・負荷テスト
・ユーザビリティテスト
ただし、TDDをやりきった場合、テストの品質を測る指標にはなりませんが、ステートメントカバレッジ(命令網羅率)は100%になります。
参考文献