前の記事 に続いてSeasar Conferenceをレポートする。
DI時代のTDD入門
次のコマは t-wada さんのセッションに出た。先日の WEB+DB PRESS vol.35 のTDDの記事を読んだ人にも呼んでいない人にも聞いてほしいとのことだった。
- TDDのテストは品質保証のためではなく、開発促進のためのツール
- テストには粒度があり、資産価値がある
- DIコンテによってテストがやりやすくなった。
- Eclipseを使いこなす
とのことである。
TDDとは
- テストコードを書く
- テストが失敗することを確認する(red)
- テストを成功させるようなコードを書く
- テストが成功することを確認する(green)
- テストが成功する状態を保ったままリファクタリングする(refactor)
このred => green => refactorのサイクルを回して開発を進めていく。
設計行為としてのテスト
TDDにおけるテストとは、"developer test"のことである。一口にテストといっても次のようなのがある。
- Developer test : 開発促進のため。設計行為である。
- Customer test : 進捗管理のため。機能要件を確かめる。
- QA test : 品質保証のため。非機能要件を確かめる。
TDDでテストといったときには品質保証などはスコープに入っていないことに注意する必要がある。
TDDのアプローチ
t-wadaさんによれば、「私たちの目的は動作するきれいなコードを作ること」である。このとき、いきなりきれいで動作するコードを作ろうとしても難しい。普通は次のどちらかになる。
- きれいだが動作しないコードを書いて、それから動作するようにする
- 汚くてもいいから動作するコードを書いて、それからきれいにする。
TDDでは後者の路線をとる。まず、テストを書いて、動作するとはどういうことであることかを規定する。次に、何でもいいからそのテストを通るようなコードを書く。この時点では、コードは動作するが汚いかもしれない。最後に、動作することを保ったままリファクタリングを行い、きれいにする。
{きれい, 汚い} × {動作しない, 動作する}のマトリクスを考えたとき、(汚い, 動作しない) => (汚い, 動作する) => (きれい, 動作する)の3象限をred => green => refactorと回り続けるのがTDDである。
Jack Reevesによれば「プログラミングは組み立てではなく。設計行為であ」り、そこにおいて
- red: 仕様の設計
- green: 仕様の実装
- refactoring: 内部設計の改善
だという。t-wadaさんは「最初から思い通りに書けるほど私たちは賢くない」「現代では、最初から思い通りに動くほど、対象は単純ではない」だから、まず動作させ、対象への間合いを詰めて、そこで状況を伺いながら方向修正するのだという。
フラクタル
t-wadaさんはTDDを実践的に行おうとすると、そこには自己相似構造が立ち現れることを指摘する。テストには粒度の違いがあり、大きなテストがredである間に小さなテストがくるくるとred => green => refactorを繰り返すのだ。TDDというと小さく回すということだけが強調されがちだが、実は小さなサイクルの上に大きなサイクルがある。大きなテストがgreenになるまでには数日から一週間程度掛かるものである。この粒度の違うテストの出現は私もたびたび経験している。
まず書くのは大きなテストだそうだ。 これは受け入れテストのようなもので、「こう動いてくれたらうれしい」ということをテストとして書く。これを最初に書いておくことにより、いつ開発が終了するのかが明確になる。大きなテストがgreenになったら終わりなのだ。
t-wadaさんはWEB+DB PRESSに書いたテスト例を挙げた。そこでは、HTTP-Unitなどを用いてWebアプリケーションの、リクエストに対して期待されるレスポンスをまず規定している。それから、アプリケーションの実現に移るのだ。
テストの書き方
次に、小さなサイクルにおける問題を中心にテストの意義、書き方を論じた。
意義
まず、テストを書くとは、設計の手順を踏むことである。実装をいきなり作るとついつい作るものに頭が言ってしまいがちであるが、テストを書くことで明確に設計という手順を踏むことができる。
また、テストを書けばこれから作るプログラムの最初の利用者は自分自身となる。メソッド名がおかしいとかパラメータが多すぎるとかそのような設計上の問題は実装前に実際に利用シーンを書いてみて、実感し把握できる。利用者の視点を最初から得ることができるのだ。ごく自然に"Eat your own dog food"を実践できる。
技法
仮実装: テストをgreenにするために最短のやりかたで仮の実装を書いてみる。
たとえば、メソッドがある値を返すことをテストしているなら、その値を固定的に返すだけのプロダクトコードを書いてしまう
- これにより、テストコードに問題があればその時点で分かる。テストコードに問題が無ければここでgreenになるはず。
テストコードの実装ステップがプロダクトコードの実装に依存しない
三角測量: 仮実装の問題点を露呈させるようなテストを追加する。
そのテストに対してgreenであるようにプロダクトコードを修正する。
- こうして少しずつ本物のコードに近づいていく
t-wadaさんによれば、仮実装 & 三角測量はまわりくどいやりかたに見えるが、その場で挙げたような単純な例ではなく、本当に複雑な実装の場合は効果を発揮するという。この手法が真価を発揮する本物の例はそれ自体が複雑なのでここでは挙げられないが、WEB+DB PRESSの6章を見てほしいとのこと。
複雑な問題を解くには、green barを出すのに時間が掛かってしまう。それを解決するのが仮実装である。そういえば、t-wadaさんの孫弟子こと id:moro さんから聞いた かくたに さんの言葉を思い出す。テストは資産だから、テストを書き上げたら早くそれをソースコード・リポジトリにコミットしたい。けれども、redの状態でコミットするわけにはいかない。そもそも、そのテストが適切なテストかどうかもわからないのに。そこで、仮実装する。仮実装してグリーンになったらコミットする。
テストが資産であるというのはt-wadaさんも再三述べている。
なお、実装イメージがあまりにも明白である場合もある。このような場合は仮実装 & 三角測量を行うには及ばないそうだ。実装イメージが明確に見えている場合はそのステップは抜かし、テストを書いたらすぐに実装を書くというのがt-wadaさんのやりかただそうだ。私の個人的な体験からは、三角測量の練習には明白な実装が見えている対象物を練習台にしたほうが良いのだけれど、いつまでもそんなことをやっていたら効率が悪いのは確かだ。「不安をテストとして行う」というのもt-wada流の教えである。一気に実装できてしまう自信があるならばそうする。不安を感じるならば仮実装を経る。
学習テスト
不安をテストとして行なうと言えば学習テストである。未知のライブラリを使って作業するとき、これで使い方が正しいのかと不安になる。実現すべき機能と、正しいライブラリの使い方と二つを同時に意識するのは難しい。そこで、「使い方はこれで正しいだろうか」という不安をテストにする。自分の理解ではこのライブラリはこう動作するはず、という内容をテストコードとして書き、ライブラリに対してテストを掛ける。
こうして書いたテストはあとあと、そのライブラリに対する格好のコード例となる、ともWEB+DB PRESSに書いてあった。
mock
DBや外部システムなどの、重かったりしてテストの足かせになる部分を仮のオブジェクトで置き換えてしまう。とはいえ、mockはあくまでも「このコンポーネントはこう動作するはず」という自分の妄想の産物なので使いすぎには注意するようにとのこと。資産価値も低いそうだ。
DBにしてもin processの軽量DBがいろいろ出てきたりしているので足かせにならなければ本物を使うに越したことはない。
はしご - 資産価値
不安をテストにするt-wada流。はしごはテストの良いアナロジーだそうだ。
- 調子のいいときは二段とばしでもいい。
- はしごを上ったらはずしてもいい。用済みのテストを捨てる。
小さいテスト、学習テスト、mockは資産価値が低い。メンテナンスコストがもったいない。
テストは資産であるが、テストのメンテナンスにもコストがかかる。粒度の小さいテストは実装の詳細な方針に依存する部分が大きく、リファクタリングするとすぐにredになってしまう。こうしてredになってしまったテストはすでに役目を終えている訳なので、もはや管理しようとせずに捨てるべきだそうだ。
Eclipse技法
- ショートカットキーの数々
わざとcompileエラーを出してeclipseに修正させる。クイックフィックスを活用
変数の宣言など自分で書くまでもない。変数を使ったコードを先に書いてしまう。当然エラーが出るのでそれをクイックフィックスする。
Quick JUnit Pluginで簡単にテストを起動
DI
DI以前は単純に実装するとクラスの結合度が高くなり、テストしづらかった。そこでFactoryを使うが、今度はFactoryが「有名人」(そこら中から参照される)、「物知り」(そこら中を参照する、責務が多すぎる)になってしまっていた。
DI以降、テスト時には簡単にモックを差し込めるし、Factoryのような問題は発生しない。DIはテストをしやすくした。
TDDのすすめ
TDDはスキル