ページの目的
テスト駆動開発の学習コンテンツのメインは
JUnitやGoogleTestなどのフレームワークを使って、RED->GREEN->Refatoringのサイクルを体験してみるような内容が多いです。
このようなテスト駆動開発の基本以外に
テスト駆動開発を実務に移すときには、対象となるプロジェクトに合わせてプロセスを定義する必要があります。
業務において重要な点は時間が経過したときにメンテナンスコストが破綻しないような保守性だと思います。
保守性が悪くなるテストの特性としては
脆いテスト:本番コードへの無関係の変更で失敗するテスト
不明確なテスト:失敗後に間違っている点、修正方法がわからない、またはテストの存在する理由がわからないようなテスト
があります。
このようなテストを量産しても、自分以外の開発者もしくは未来の自分がテストを読んだときに
テストケースのコードを精読しない対応できないような状況が多発してメンテナンス工数が確保できなくなり、テストは衰退していきます。
これら悪い特性のテストを防ぎ、保守性を高めるには理解容易性を高く保つ必要があります。
理解容易性のカギを握るのは
「テストを動作するドキュメント」
として管理していくことになります。
この記事では
「テストを動作するドキュメント」
を作るための方針を
いくつかの学習コンテンツ(t-wadaさんの講演が一番参考になりました)
を参考しながら記載していきます。
参考資料
TDD Boot Camp 2020 Online #1 基調講演/ライブコーディング ★おすすめ!
テスト駆動開発
Googleのソフトウェアエンジニアリング
前提知識
- テスト駆動開発の基礎(RED->GREEN->Refatoringを知っているくらい)
- GoogleTest
以下本編
解きたい問題はAtCoder ABC 024のA問題に具体的な数値をいれた以下とします。
ABC動物園は、高橋王国で一番の人気を誇る動物園です。
ABC動物園の入場料の設定は以下のようになっています。
子供 1 人あたり 100 円
大人 1 人あたり 200 円
合計人数が 20 人以上の団体は 1 人あたり 50 円引き
今、子供 S 人、大人 T 人からなる団体が入場しようとしています。 この団体が合計で支払わなければならない入場料を求めてください。
TODOリスト(≒ 仕様書)を書く
はじめに今から実装したいSWのTODOリストを作成します。
TODOリストには実装するSWの期待する振る舞いを記載していきます。
TODOリストは最終的には、実装するSWの振る舞いを体系的に整理したドキュメント、つまり仕様書として機能することになります。
最初は粒度は気にする必要はなく、とにかく書き出していくことが大切です。
期待動作から考える理由としては
・「正しい結果とは何か?」と「それをどう実装するのか?」を同時に考えるのは脳の容量的に厳しいためどちらか1つから考える
・期待値はSWが実現すべき目的なので、期待値を最初に決めておくと各作業で目的を見失わないため
です。
期待値を別の言葉で表現すると「利用者の始点から実装したらこうなってほしいというシナリオ」です。
書き方のコツとしては
**「…という前提条件のもとで」、「…の場合」、「その場合は…すべきである」**を意識することです。
期待動作を書き出した後は、問題を小さくしていきます。
最終的には仕様書としたいので、粒度を意識して階層化も行っていきます。
同じ階層にある文言は合わせて書く(文章の構成は同じだが値だけが異なるイメージ)ことが望ましいです。
とりあえず↓な感じでTODOリストを作りました。
-
子供は100円/人。大人は200円/人。総数が20人以上の場合は50円/人割引になる。このとき子供の人数と大人の人数がが与えらた場合の入場料を計算する。
-
合計人数が20未満の場合の入場料を求める。
- 子供が3人入場した場合は3×100=300円
- 大人が3人入場した場合は3×200=600円
- 子供が3人、大人が3人入場した場合は3×100+3×200=900円
-
合計人数が20以上の場合の入場料を求める。
- 子供が30人入場した場合は30×100-30×50円
- 大人が30人入場した場合は30×200-30×50円
- 子供が30人、大人が30人入場した場合は30×100+30×200-(30+30)×50円
-
合計人数が20未満の場合の入場料を求める。
TODOリストとテスト実装方法の対応付けを決める
TODOリストを作成したので、次はTODOリストに対応させた実装をどのようにするかを考えます。
※テスト容易性の高いテストから書き始めてまずは「動くコード」を作るという方法もありますが、
個人的には手戻りしたときの工数が惜しいので、フォルダ/ファイル構成、命名規則などは事前に設計しておいたほうが良い気がしています。
テストコードは仕様書のように機能させたいので
仕様書であるTODOリストと、テストコードは1対1に見えるようにしたいです。
使用するテスティングフレームワークによってできることが変わってきますが、ここではGoogleTestで考えます。
TODOリストのような見た目にするにはテストを入れ子にして階層化して記載する必要があります。
また、理解容易性のためには母語で記載できることが望ましいです。
Googleテストでは
・テストの階層化はテストスイートとその中にテストケースがある2階層構成でこれ以上深くはできません(できたら教えてほしい。。)
・日本語不可
です。
解決策ですが
階層化に関してはIDEの機能で補完するが1案です。
(ex. Visual Studioの場合napespaceやclassで補完できます https://docs.microsoft.com/ja-jp/visualstudio/test/run-unit-tests-with-test-explorer?view=vs-2022)
ただ、特定のツールに依存させることはなるべく避けたいです。
また日本語で記載したいことも考慮すると、ディレクトリ内にTODOリストをtextまたはmarkdownで置いてしまうことが解なのかなと思っています。
DRY(Don't Repeat Yourself)の原則に反していますが、ここは目をつぶります。母語でななめ読みして内容を理解するメリットのほうを優先します。
具体的な階層化の対応付けは
1階層:ファイル名
2階層:テストスイート名
3階層:テストケース
とします。3階層あれば足りる前提ですが、現実大体は足りるのではないかと思っています。4階層にすると理解容易性も落ちてくるので3階層で制約しても良いという思想です。
テスト名を考える
TODOリストの階層構造に対応した構成は考えたので
次はテスト名について考えます。
優れたテストの名称は
システムに対して行われる動作と、期待されている結果の両方を説明する
ことです。
テストは呼び出されることはなく、読まれるだけなので冗長な名前はある程度許容されます。
迷った場合はshouldで開始するといいかもしれません。
参考:Testing on the Toilet: Writing Descriptive Test Names
ですが、今回はTODO Listがあり、TODL Listと対応するものを決めているので
あまり頑張って考える必要はないかもしれません。
TODO Listを仕様書の一部とするのが邪道で、コードだけで表現したい場合には名前にはこだわったほうが良いと思います。
最終イメージ
以上を実装した
最終的なイメージは以下のようになります。
仕様書であるTODO Listとコードが対応しており、理解容易性も高くなっています。(と、思っています。。)
※最終系と書きましたが、ここからRed->Green->Refactoringのサイクルを回していくことになります。