いや、どこに「TDD 原理主義者」が居る訳ではないのですが、仮想敵としての「TDD 原理主義者」です(笑)。
早速、仮想な「TDD 原理主義者」の主な言い分を列記すると。
- 全てのプログラムは、テストコードから作成しなくてはいけないッ!!!
- 変更が発生した場合は、テストコードを修正してからッ!!!
- 全てのメソッドはテストコードを書くべきであるッ!!!
- UI 部分もいずれは xUnit を使わないといけないッ!!!
- 実コードとテストコードは同じ言語でなくてはいけないッ!!!
な言い分がある…としますね。ええ、仮想ですから。実はケントベック著「テスト駆動開発入門」をざっと読んだだけだと上記な「原理主義」的な発言をし始めます。中身は、Java で書いてあるのですが、これをひと通り自分の使っている言語でやっていくと上記のような発言が「原理主義」的な発言でしかないことが分かります。
という訳で、以下、私なりの論破を書いておきますね。
■全てのプログラムは、テストコードから作成しなくてはいけない?
まあ、原則としてはそういうほうがベターなのですが、最近のように IDE 全盛の時代になり、コーディングをするときにインテリセンス(補完機能)が付くとなると作り方は別ですよね。テストコードを作る時には、まだ母体(テスト対象のコード)が無い状態では、当然のことながらインテリセンスは効きません。コンパイルさえも難しいわけです。
なので、テスト駆動にもあるのですが、いちど母体のひな型を作ってからテストコードを書きます。
…ということになっていますが、もうちょっと先に進むと、テストコードで母体の使い方を模索した後に本体のコードを書くというのが、実はユーザーインターフェースとしては良いことが分かっています。というのは、テスト駆動を行う場合(あるいは xUnit を導入する場合)いきおい、テストコードを書くために、関数やクラス名を先に決定しようとしてしまいます。実際のコードは「書いてみないとよくわからない」という前提があるのに、クラス名や関数名やパラメータを先に決定しようとする矛盾があるわけです。なので、本来のアジャイル的な手法を TDD に導入するならば(実際は、アジャイル手法を支える手段が TDD なのですが)、
- クラス、関数の使い方をテストコード=実験コードを使って書く。
- ひとまず、コンパイルが通るまでの本体コードを書く。この時点では、Assert はいれない。
- いくつか繰り返して、利用できるコードができたら、Assert を入れる。
- 以下は TDD の手法で繰り返し/リファクタリングする。
という流れになります。1,2 の「模索」が抜けると、TDD なのに一気にウォーターフォール地獄に突入するので注意が必要です。
■変更が発生した場合は、テストコードを修正してから?
まあ、原理的にはそうなのですが、実際はどちらでもいいのですよ。と言うのも、ある程度きっちりとテストコードを書いていると、
- 本体コードを修正することにより、他のコードでエラーが出る。
ことが多いのです。本来のテストコードではなくて「影響範囲」と呼ばれるコードの範囲がエラーになるのです。実は、作業量的にはこちらの「影響範囲」のエラーのほうが重要なのです。TDD を使う場合、通常はオブジェクト指向言語を使う訳で、それに従うと「隠蔽化」がうまくできていれば、この手の「影響範囲」は非常に少なくてすみます。場合によっては全くありません。ですが、いろいろな static 関数やもろもろの設定が入り混じっているとこの「影響範囲」が非常に大きくなります。
こういう場合には、そこに手を入れるのは「危険」だから、別の方法を模索せよッ!!! という信号をキャッチしているのですね。というわけで、いきなりコードを修正したほうが作業量的に良い場合があります。
■全てのメソッドはテストコードを書くべきである?
これは、ケントベック氏自身が最近言っているように、全てのメソッドに対して書く必要はありません。単純なget/setのプロパティや、間違いようのない単純な計算までをテストコードに入れるのは時間の無駄です。そういえば、Visual Studio 2010 ではテストコードの自動生成があったのですが、VS 2012 ではなくなっています。「自動生成」自体は、あまり良い機能とはいえませんが、先に言うように「ひな形」を作るのは適しています。この件に関しては、M$ が原理主義に陥ってしまったのかもしれません。
なので、private メソッドは普通テストコードを書きません…が、複雑な private メソッドの場合は必要ですよね。そういう場合は、適当にコンパイルスイッチを使って public にしてテストをするコードを作ったほうがベターです。リフレクションを模索する方法もありますが、public するだけならば、作業量としては楽ですからね。ええ、メモリの配置が違うので、バイナリ構成が違う、というパターンもあるのですが、それほどシビアではない場合はよいでしょう、ってことで。
■UI 部分もいずれは xUnit を使わないといけない?
ここ、私も10年前はそう思っていたのですが、TDD の本質といいますか作業効率を上げるというアジャイル的な指針に沿うならば、無駄なことはやめよってことですね。UI の場合は、マウスクリック、テキストの入力、ウィンドウの移動など様々な要素が絡み合っています。その「絡み合っている」部分、曖昧な部分を、xUnit という「きっちり」とした部分で対応させるのはどうかな?と思う訳です。なので最近は、UI 関係のテストは、手作業に徹するようにしています。それ以外のライブラリの部分を xUnit を使う訳です。
なので、この方法を取るためには、MVC パターンを使うか、それに似たパターンでロジックの部分で xUnit を使うという設計が必要になるのです。
かつて、ガラケーの開発をした時に、ガラケーアプリのテストはひたすら人間がやっていました。親指でぽちぽちとひたすら1日8時間テストを繰り返すわけです。ゲームのテストもそうですが、いやぁこれはちょっと…人間のやることではないな、と思ったものです。ええ、派遣さんがやるんですけどね。
スマートフォンの場合はどうなんでしょうかね?シミュレータがあるから多少マシになったかもしれませんが、テスターの苦労は大変なもので、ええ本音を言えば関わりたくありませんッ!!! ってな位だと思ってます。
なのでこれを避けるために取る方策としては、xUnit で UI テストを自動化するのではなく、
- 設計の段階で、xUnit を使う部分を決めておく。
- 設計の段階で、UI テストを行う人件費/作業量を算出しておく
ってことですね。そうすると、人的作業の費用対効果が分かるので、このインターフェースは避けたほうがいいぞ、ってことになります。
■実コードとテストコードは同じ言語でなくてはいけない
これ、昔から言われていたことで、Java のテストをするならば、Java で書く。C# のテストをするならば、C# で書く。というのが「原理」です。この原理の理由としては、自分のテストコードは自分で書く、自分でリファクタリングをする、というパターンがあるからなんですよ。他の言語を使うと、頭を切り替えないといけないので、コーディング→修正のサイクルが遅くなってしまうという訳です。また、先の UIDD の理念から、テストコード=ライブラリの利用方法になるので、同じコードが望ましいわけですね。
が、なにも「同じでなくちゃダメ」って訳でもありません。ストアドプロシージャのテストをするのに、SQL 文を駆使しなくてもよいし、Fortran のテストをするのに、Fortran 自身で書く必要もありません。
- 効率的にコンピュータを動かす言語(SQL や Fortran など)
- テストを効率的に書きやすい言語
とは別であっても良いのです。
ちなみに、いくつか xUnit の「方言」がありますが、どれを使っても一緒です。と言いますか、長く使えるライブラリを使うのが無難ですね。私の場合、未だに古い CppUnit を使ってテストコードを書きます。実コードのほうは色々と新技術を使ってもいいのですが、xUnit の場合は古臭いパターンを使って、Assert を書くほうがよいのです。と言うのも、テストコード自体を書くのに頭を使いたい訳ではないですよね?本コードを作るのに頭と時間を割きたいのです。って理由が第一です。
さて、ここまで書いて「立ち上がれ TDD 原理主義者」というのも考えたのだけど、やめておきましょう。多分、いつまで経っても立ち上がらないし。