なぜテストが開発を駆動するのか
はじめに
TDD は Test-Driven Development を省略したもので日本語では『テスト駆動開発』という語が訳として与えられている。
TDD は現在多くの人に認知されていて、多くの実践者がいると思う。
私も TDD というスタイルが好きでよくそのような開発をする。
これまで、開発者の方と TDD について話すと『どうやる』の方に興味がいって『なぜ』の部分が置き去りになっていると感じることがあった。
例えば、『どうやればいいかわからない』といわれたのだが、TDDの典型的なお作法自体は知っているようなのだ。これはそもそも『なぜ』TDDをやりたいのかがわからないのではないのかと思った。
TDD はその名の通りテストでソフトウェア開発を駆動させるための開発スタイルだ。
なので、TDDをより効果的に行うためには、ソフトウェア開発がどのように行われるかを理解し、『なぜ』テストが開発を駆動するかを理解 (納得といってもよさそう) することが必要だと考えている。
また、『なぜ』がわかれば、よくある(まだあるといってもいいかもしれない)『TDDをやれば品質が担保されるのか?』みたいな疑問にも一定の理由をつけて回答できると思う。
ということで、この文章では私が考える TDD をやる理由を整理したいと思う。
ソフトウェア開発のステップ
ソフトウェアの開発ステップをどの広さで見るかで変わってくる。
例えば、広く見たときは要件の定義から試験までの工程と捉えることができる。
ここでは、主にTDDの話なので比較的狭い領域、具体的には要件がある程度固まっている状態からソフトウェアとして実装するまでとする。
開発作業をするときには大きく分けて以下の2つのステップを繰り返しているものだと考えている。
- 実装対象についての情報収集・整理 (インプット)
- ソフトウェアモジュールの設計・インターフェースの定義など
- 整理された情報をコードで表現する・実装する (アウトプット)
- ロジックをソフトウェアコードで実装するなど
開発をどのようなスタイルで行っているかに依存するが、多くの場合インプットとアウトプットは明確なステップとして認識されず、頻繁に行ったり来たりする状態を繰り返すこと事が多いと思う。
インプットの手段は非常に多様だ。UMLなどを利用してソフトウェアモジュールの設計をすることもあるだろうし、利用するサードパーティAPIのSDKについてドキュメントを読むかもしれない。
重要なのは、『インプット』 ≠ 『コードを記述する行為』が 常に成立するわけではない ということだ。例えば、SDKの動きを確認したくてコードを書いているのならそれは『アウトプット』ではなく『インプット』だ。
それが何?と思う方もいるかも知れないが、『インプット』のためであるはずのコードをそのまま本番コードとして利用したり、何を作るかがぼんやりしたまま書き進めたりしたコードは望ましい状態にならないことが多いと考えている。
動くものはできる。だけど、動くだけ。みたいな結果になる。
私はこの開発中のステップ(あるいは状態と表現してもいいかもしれない)を正しく認知しながら秩序を持って遷移させることが開発を強く促進すると考えている。
TDD の流れと開発ステップの対応
TDDの典型的な流れを大まかに書くと以下のようなものだ
- テストの実装
- テストが通るように実装
- テストに通る状態でリファクタリング
1 は『インプット』に対応する。つまり、テストの実装を通して、これから何を実装するのかを確定させようとしている。
ある機能を実現させるにあたって、どのように動くと良さそうか?という問に答えられないとテストを実装することができないので、必要に応じてモジュールの設計やインターフェースの設計などが行われていないといけない。そして、それらをすべて含めた『これから何を作るのか?』に対する表現としてテストを記述することになる。
2, 3 は『アウトプット』に対応する。つまり、テストを通過させることを通して予定していた要件を満たす実装を作成し、要件を満たした状態を維持したままプロダクションコードとしてふさわしい形に推敲をする。
やや雑なことを承知で言えば、『アウトプット』は要件満たす作業であり、2 は機能要件を満たす作業であり、 3 は (一部だが) 非機能要件を満たす作業であるといえる。
開発ステップの中で TDD が果たす役割
前述の通り、開発をすすめる中で『インプット』と『アウトプット』の境界が混同されることがある。一方で、『インプット』は何を作るかを決定するための行為であり、『アウトプット』は予め決めた物を実装するという行為であるため、これらを混同すると『作りたかったものが作れていない』や『何を作りたいかを考えないで作っている』という状態になる危険性がある。
TDD はこの開発ステップに一定のリズムを与える。
今、自分が行っているのが『インプット』か『アウトプット』かを把握することができるため、アウトプット中に『インプット』をしたくなったとしてもそれを律することが容易になる。
TODO リストを用いて、今の目的外のことをやりたくなったらそのリストに積むという作業が説明されることがあるが、そのためだ。
また、ステップで必要なことを満たせているかの目安になる。
例えば、テストが記述できなければ、実装の対象が大きい、要件が理解できていないということが示唆される。モジュールを分割したり、要件のヒアリングを追加で行ったりするだろう。『テストを記述する』という行為そのものが『アウトプット』に十分な『インプット』が行われているかを評価してくれると言える。
なぜテストが開発を駆動するのか
ここまで記載したとおり、テストを書くという行為は、これから作るソフトウェアに関する情報を増やすという側面がある。
ソフトウェアがどのように実装されているべきかという情報を、文章などよりもより実装に近いテストという形で表現できる。
これは単に文章で列挙されている仕様よりも、より多くの情報を提供してくれる。
ソフトウェアには機能的な要件と非機能的な要件が求められる。
機能的な要件をテストで適切に表現ができていれば、機能的な要件を満たしながら非機能な要件を満たすためのコード改変を行うことができる。
2つの要件を同時に満たそうとするのは難しい。テストがあればこれを別々に解決できる。
ソフトウェアを開発する時『インプット』と『アウトプット』という2つのステップが存在する。
TDD のスタイルではこの2つのステップの移行に秩序をもたらす。
おわりに
ソフトウェア開発を行うときにどのような状態を遷移するかという点にフォーカスしながら、なぜテストが開発を駆動するかを考えた。
本筋ではなかったので深く触れなかったが、そもそも何を作りたいかをテストで表現するという事自体が難しいという点があると思っている。
これに対しては、実装対象が大きすぎるとか作ろうとしているものの粒度に問題があると思う。そして、うまく対象を小さく分けるという行為ができないまま、 (プリントデバッグとかしながら?) 最終的に動くものを作るという状態になっているのではないかと想像している。
TDD や単体テストを書くということをやっていない人に理由聞くと、「時間がかかる」という答えが返ってくる事があるのだが、仮にこのような状態だと確かに時間がかかるな。と勝手に想像している。
ここで記載した内容が TDD がうまくできなくて困っている方の理解を深めることに寄与すると期待している。
参考
Discussion