軽量なテスト駆動開発を目指して #TddAdventJp

これは、TDD Advent Calendar jp:2012 の16日目のエントリーです。前日のエントリーは、@pocketberserkerさんの「Specs2のParameterized Testのはなし」でした。

ご存じの方も多くなっていると思いますが、「テスト駆動開発(以下、TDD)」とはテストコードを先に書くテストファーストを基盤とした開発手法です。先にテストコードを書く事により、これからどのようなプロダクションコードを書こうとしているかを明確にすることができることが特徴です。このため、テストの技法というようりは設計の技法です。

テスト駆動開発を実践することにより多くのメリットを得ることができます。このことは2011年のAdvent Calendarで言及しました(TDDを学ぶべき10の理由 #TddAdventJp)。TDDは簡単に導入することができる一方で、実践するのは非常に難しいスキルです。特に簡単な計算機などのTDDはできたとしても、その後にちょっと複雑な、そう自動販売機アプリケーションのようなプログラムをTDDで開発する所に壁がある気がします。

そこで、今年のエントリーでは「軽量なテスト駆動開発を目指して」と題して、そんなTDDのやり方をなんとなく習得した人が、TDDを実践していく上でのヒントとなればと思います。自分がテスト駆動開発を行う時にどんな考えで最初のテストコードを書いているかをまとめたので、参考になれば幸いです。

イメージ図

TDDが難しいワケ

TDDで難しいワケとして、どのようにクラスを分割し、どの程度テストすれば良いかの判断が難しいことがあります。これに対するひとつの回答は「不安がなくなるまで行う」ですが、不安は簡単になくなるものではありません。
そして、TDDを習得することによって、より不安になるというジレンマがあります。特にある程度大きな機能を分割し、TDDを行っていくと、「もう少しテストを追加しないと...」と不安となり、過剰にテストを書いてしまいがちです。「本当にこのクラス設計で大丈夫か?」と不安になってしまうと、最初のテストを書くことが不安になり、最初の1歩を踏み出す勇気が持てません。簡単なメソッドでもテストを書かないと怖い…と思ってしまうと、幾らテストを書いても開発は終わりません。
不安を安心に変えるためのテスト駆動開発が、むしろ開発者を不安にさせている、そんなことはないでしょうか?

はじめの不安は何か?

テスト駆動開発の肝は「不安を安心に変換するサイクルとリズム」です。
もし、最初のステップで「このクラスのこのインターフェイスは本当に使われるだろうか?」といった不安があった場合、その状態で「レッドーグリーンーリファクタリング」とTDDのサイクルを回してもその不安は消えません。2回目、3回目のTDDのサイクルを回すことにより「もし、これがまったく使われなかったらどうしよう…」とより不安が大きくなることもあるでしょう。
はじめに解消する不安を選択することが重要です。

いつ、テストをやめるのか?

テスト駆動開発では、TDDのサイクルを回しながら、不安がなくなった時点でサイクルを止め、次の機能の実装などに移ります。
よくあるケーストして、例外のテストをどこまでしたらば良いか困ったことはないでしょうか?Javaであれば、パラメータにnullが渡された場合の挙動など、「必要となるか解らないけど、不安だからテストしなきゃ…」となる場合があります。不安なのでテストするは正しいのですが、不安な理由は「良く解らない」からなのです。
どこでテストをやめれば良いかを把握するには、何が不安となっているかを知ることが重要です。

軽量テスト駆動開発とは?

軽量テスト駆動開発とは、行うべきテストを見極め、必要なテストコードしか書かないテスト駆動開発です。当たり前のような話ですが、自分の経験的にもテストを書きすぎてしまう傾向があったため、こんなネーミングにしました。
その肝は、何を不安と考えているかを明確にすることと、不安とならないテストは書かないことのふたつです。
なお、自分の感覚としては最盛期に比べ、テストの数は2/3くらいになった気がします。代わりにテストパターンを増やしたり、網羅的にテストを行えるように工夫したりできる余裕が生まれています。

それでは、簡単にですが自分のアプローチを紹介します。

はじめに外部的振る舞いを確認しよう

はじめに解消する「不安」は、アプリケーションの要件を満たす外部的振る舞いです。
外部的振る舞いは、要件定義や外部設計では(システムの)外部境界とも呼ばれ、内部実装に依存しない、アプリケーションの入出力の仕様です。
例えば、FizzBuzz問題*1であれば、指定された値までの数値または文字列を出力することが外部的振る舞いです。

ここで不安はないと感じるかもしれませんが、自分は文章だけの仕様は安心できません。

具体的な例を考える

外部的振る舞い(仕様)では、システムへの要件は列挙されていますが、具体的な例ではありません。このため、誤解して実装してしまうかもしれません。仕様自体に問題があることもあります。このような問題を回避するには、具体的な値を例として考える事です。
FizzBuzz問題であれば、例えば10を入力した場合に「1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz」と表示されるように、ひとつだけ例を作りましょう。

これでひとつ不安が解消されました。

検証可能にする

外部的振る舞いを例にすることで、不安がひとつ解消されましたが、まだTDDを行う段階ではありません。次に行うことは、例を検証可能な形(=テストコード)にすることです。
FizzBuzz問題では入力値に対する標準出力が外部的振る舞いになります。これを検証する方法はあるかもしれませんが、思いつかないことやプログラミング言語の制約などでできないかもしれません。

重要な事は不安を素早く安心に変える事です。

例えば、Javaで「リストをループして標準出力する」というプログラムであれば不安はないでしょう。つまり、入力値に対し、FizzBuzz問題を満たす文字列のリストが取得できれば、安心して標準出力できます。
ここでは、重要な事をふたつ行っています。それは、問題から「安心な部分や検証が困難な部分を省いていること」と「入力値を検証可能な出力値に変換するインターフェイス」を作っていることです。

最初のテストを書く

これで最初のテストコードを書くことができます。
JUnitでもCucumberでもRSpecでも自由にテストコードを書くことができるでしょう。なぜならば、テスト対象への具体的な入力値と戻り値、そして期待値があるのですから。もし、ここでテストコードを書くことで詰まるようであれば、ユニットテストについて学ぶ必要があります。

テストをグリーンにする前に

ここまで、システムを外側から見てテストコードを書いてきました。ここで実装を書けるよう出れば実装を進めます。書けるかどうかの判断は簡単です。不安に思わず、実装が書けそうであれば実装すれば良いのです。FizzBuzz問題であればここで解決する人が多いと思います。

問題はここで不安に思った場合です。

不安をリストアップする

実装に不安を感じたならば、その不安を言語化してください。
ちょっと無理もありますが、FizzBuzz問題の実装で不安と感じるのは「3と5とその公倍数の判定が不安」とか「それらの場合に正しい文字列を返せるか不安」といったところでしょう。

不安を検証可能にする

不安がリストアップされたならば、具体的な例を作り、不安を軽減した後、それらを検証可能なテストコードにします。FizzBuzz問題であれば「1を与えると1を返す」「2を与えると2を返す」「3を与えるとFizzを返す」メソッドがテストできれば不安はなくなりますね。

レッド−グリーン−リファクタリング

不安がなくなる段階まで落とし込んだならば、今まで学んだようなTDDを行ってください。恐らくは計算機クラスのような簡単なテストだったり、ちょっとだけテストダブルを使えば解決するような簡単なテストになっているはずです。

最初のテストをグリーンにする

幾つかの小さな不安、もしくはその不安の下のもっと小さな不安などをひとつひとつ解決していくことにより、最初のテストをグリーンにする準備が整うはずです。上から考えているため、無駄なコードもないでしょう。もしかしたらば、より良い設計が思いつくかもしれませんが、それでも無駄ではないと思います。

不安でない事はテストしない

このようにTDDのサイクルを繰り返していくと、FizzBuzzのメソッドでは引数にゼロが与えられたり、マイナスが与えられたりしたらどうしよう…と考えるかもしれません。
しかし、少し前に戻って再確認してください。要件にそのようなことは想定されていたでしょうか?もし、解らないならば要件や外部仕様の部分で決めることだと解るはずです。

ここで外部仕様として「1以上の整数を与える」という制約を加えてしまえば、FizzBuzzの内部メソッドではその考慮をしなければならないという不安がなくなります。もちろん、1つ上のシステム境界にとして振る舞うインターフェイスでは考慮しなければなりませんが、少なくとも内部メソッドはゼロ以下の値で呼び出されないと、「安心」できるはずです。

ほら、テストしなくても大丈夫!

ここでTDDのサイクルをやめることができます。

アウトインサイドの開発

これって「アウトインサイドの開発」ってやつでは?と思った方はJUnit実践入門かRSpec Bookを読んだ人だと思います。考え方としては受け入れテストによる振舞駆動開発を起点に、そこからユニットテストによるテスト駆動開発を行う流れであるため同じ考え方です。
しかし、重要なことは、振舞駆動開発かテスト駆動開発かではなく、受け入れテストかユニットテストかでもなく、この流れが、もっとも自然に、効率良く、不安を解消することです。

軽量テスト駆動開発

テスト駆動開発は、不安を安心に変える慎重な開発手法です。安心を手に入れる代償として大きなコストを払う必要があります。勿論、そのコストは、これまでデバッグを行いながら払ってきた分であるため、慣れることによってある程度は解決する部分かもしれません。しかし、よりテスト駆動開発の生産性をあげようとしたならば、どれだけテストを省略するかが肝になります。テストは重要ですが、本当に必要なテストを見極め、不安になる部分のみに注力すれば圧倒的にテスト数を減らせることでしょう。

不安に感じない部分は省略することで、テスト駆動開発は軽量でもっと使いやすい開発技法になるのではないでしょうか?


明日は、テスト駆動開発の伝道師 id:t-wada さんのエントリーです。

*1:1から順に指定された値まで数字を出力する。ただし、3の倍数の場合はFizz、5の倍数の場合はBuzz、3と5の公倍数の場合はFizzBuzzと出力する。