テスト駆動開発

テスト駆動開発  とは、プログラム開発手法の一種で、プログラムに必要な各機能について、最初にテストを書き、そのテストが動作する必要最低限な実装をとりあえず行なった後、コードを洗練させる、という短い工程を繰り返すスタイルである。多くのアジャイルソフトウェア開発手法、例えばエクストリーム・プログラミングにおいて強く推奨されている。date=2021-03はビヘイビア駆動開発へと発展を遂げている。

1. 開発サイクル
---------

最も基本となる開発サイクルは以下のようになる。

失敗するテストを書く
できる限り早く、テストに通るような最小限のコードを書く
コードの重複を除去するなお、テストの実行環境ツールであるxUnitでは、テストの失敗を赤いバー、成功を緑のバーで通知するため、上記のサイクルは Red/Green/Refactor と称される。

より実践的には、to-doリストを組み合わせることにより、以下のような手順で開発する。

まず、現時点で分かっている範囲で、テストする必要がある項目を列挙する。なお、このリストは、テストの必要性がわかった時点で適宜項目を追加していく。
このリストから1つ選ぶ。これは、実装できそうでかつ重要なものを選ぶ。このとき、テストの記述が容易でも、Fake Itでしかコードを記述できなさそうなものは後回しにする。実装できそうなものがない場合は、列挙した項目の粒度が大きすぎることを意味する。そのため、そのテストを実装するための前提となるような、より小さい粒度の項目を作成し、それをリストに加える。
選択した項目について、テストを作成する。このテストは、現在の実装を用いると失敗するように記述する。
コンパイルに必要な最小限のコードを追加した後、実際にテストを実行し失敗することを確認する。期せずしてテストに通った場合は、意図しないことが起こっていることに注意し、テストが失敗するまでコード本体を変更しない。
できる限り早く、テストに通るようにコード本体を記述する。この段階では、テストをパスさせるためにどんなことをしても良い。具体的には、次の3つの方法が挙げられる。
実装が自明な場合はそれを記述する。
テストに要求される値そのものをハードコーディングする。
Fake Itの後に、次の段階へ進めず漠然としている場合は、さらに別のデータを用いたテストを追加し、その2つのテストの共通点を見出して助けとする。
テストが通ることを確保しつつ、コードそれ自体やコードとテストの間にある明示的・暗黙的な重複を取り除く。通常、リファクタリングとは、コードの意味を変えずにコードを再構築することをいう。なお、この「コードの意味」とは、テストが通ることを言う。また、ここで取り除く重複には、形式的な重複だけでなく、意味的な重複も含まれる。例えば、Fake Itでハードコーディングしたものは、おおよそ実際にはどこからか得られるはずのパラメータを、別の値を用いて算出したものであるため、これを重複と見なす。そして、重複を取り除くことで、ロジックが抽出される。
実装した項目をリストから削除する。このとき、作業中にテストする必要性があるとわかった項目に、実質的に振り変わるかもしれない。テストコードは、最初から自明であるとは限らない。むしろ、コード本体と同様、最初は具象的なテストを行ない、これによって知見を得た後に、テストを書き直したほうがよい。また、テストコードから導かれるコード本体は、リファクタリングの過程によって、あるいはテストが成熟するに従って、最終的な目的とするコード本体のテスト用スタブに変わっていくかもしれない。早い段階でテストとコード本体を分離して管理するのはあまり意味がない。テストやコード本体が成熟していくにつれ、テストの記述が抽象的・間接的になり、リスクが導入される。しかし、テスト駆動開発のテストの目的は、開発者の正しさへの確信を裏づけするためであり、それが保たれているならば問題はない。

テスト駆動開発で用いられるテストは、品質のためのテストではない。したがって、コード本体とは独立してあらゆるケースを網羅するテスト、すなわち「テストそのものが価値を持つようなテスト」を目指しているわけではない。テスト駆動開発におけるテストとは、コード本体とテストを合わせて検討することで、開発者がその正しさに確信を得るようなものである。したがって、開発者の確信に少しも寄与しないテストは、むしろ積極的に削除を検討する。

テスト駆動開発を実施するには、テストを自動的に実行できる環境が必要である。そのような環境としては、JUnitやNUnitといったものが挙げられる。なお、このテスト実行環境は、コンセプトが単純であり、かつ非常に強力であることから、実行環境そのものをテスト駆動開発で自作するのもよい。ただし、そのテストツールをテストするツールはないことから、しばらくは慎重な人の判断でもってテストの代わりとすることになる。

2. 利点
-----

"Clean code that works." がテスト駆動開発の目標である。しかし、最初からきれい  かつ動く  コードを書くのは困難である。そこで、テスト駆動開発では、まず動くコードを作成し、次にこれをリファクタリングすることできれいなコードに整形する。
コードがテストに通ることにより、手戻りが発生しないことを確認できる。逆に、Fake ItやTriangulateなど、実装するものの見当がつかなくとも、頭で考え込むのではなく、コードを書くことで具体化していきながら、開発を進めることができる。テスト駆動開発は着実に開発を進めることができる開発手法である。
設計に関する決定のフィードバックとして、それを具体的にテストコードに記述することで、その設計の良し悪しが判断できる。実装に関する決定のフィードバックは、テストの失敗や通過によって通知される。両者とも、採用の決定から短い間隔でフィードバックを得ることができる。
テスト駆動開発は、あくまで開発のための手法であり、そこで得られるテストは副産物である。しかし、その開発手順から、コード本体は、テストをパスさせるために記述されたものである。そのため、理想的なテスト駆動であれば、カバレージは100%になる。欠陥やバグは、非常に少ないことが期待される。
過度な抽象化はむしろ柔軟性を失わせ、保守のコストもかかる。そこで、具象的なコードから始めて、次第にそれを抽象化していくことで、適切な抽象度で抑えることができる。つまり、現在の仕様に対応できるだけのシンプルさを保つことで、逆説的に将来の仕様変更への対応が容易になるのである。

3. 問題点
------

4. 適用の注意
--------

テスト駆動開発に適合させるのが難しいものに、以下のものが挙げられる。

セキュリティソフトウェア
並列処理・並行処理前者は、盲目的にその良し悪しが判断できないためである。後者は、実行環境におけるマルチタスク・マルチスレッドの動作による非決定論的な要素が絡み、再現性に問題があることから、やはり盲目的に良し悪しを判断できないためである。不具合がないことが前提とされているため、その点に関しては、テスト駆動開発を導入することで、一定の効果はある。

また、以下のものについても、テスト駆動開発が難しいとの指摘がある 。

グラフィカルユーザーインターフェイス  を扱うもの
分散オブジェクト
データベーススキーマ
サードパーティコードや、外部ツールで自動生成されるコードその他にも、既に構築してあるコードに対して、テスト駆動開発を導入するのも難しい。なぜなら、それらのコードはテストがしやすいようには作られていないことが多いため、先にリファクタリングをする必要があるが、リファクタリングを行なうにはその動作の保証をするためのテストが必要だからである。これはある種のデッドロックやジレンマを生み出す。また、やってはいけないことは、片っ端からテストを書いたり、リファクタリングを行なったりすることである。変更する範囲を制限することを決心し、何らかのフィードバックを得ることを前提としてコードを変更していくことが必要である。

5. 脚注
-----

6. 参考文献
-------

7. 関連項目
-------

ビヘイビア駆動開発
アジャイルソフトウェア開発
エクストリーム・プログラミング
ソフトウェアテスト
リグレッションテスト
ユニットテスト・フレームワーク一覧
xUnit
JUnit
TestNG
テスト自動化
チケット駆動開発
和田卓人

8. 外部リンク
--------

[1]: テストファーストとテスト駆動開発との違い、テスト駆動開発の具体的な手法について書かれている。