前の記事に続いてSeasar Conferenceをレポートする。
次のコマはt-wadaさんのセッションに出た。先日のWEB+DB PRESS vol.35のTDDの記事を読んだ人にも呼んでいない人にも聞いてほしいとのことだった。
とのことである。
このred => green => refactorのサイクルを回して開発を進めていく。
TDDにおけるテストとは、"developer test"のことである。一口にテストといっても次のようなのがある。
TDDでテストといったときには品質保証などはスコープに入っていないことに注意する必要がある。
t-wadaさんによれば、「私たちの目的は動作するきれいなコードを作ること」である。このとき、いきなりきれいで動作するコードを作ろうとしても難しい。普通は次のどちらかになる。
TDDでは後者の路線をとる。まず、テストを書いて、動作するとはどういうことであることかを規定する。次に、何でもいいからそのテストを通るようなコードを書く。この時点では、コードは動作するが汚いかもしれない。最後に、動作することを保ったままリファクタリングを行い、きれいにする。
{きれい, 汚い} × {動作しない, 動作する}のマトリクスを考えたとき、(汚い, 動作しない) => (汚い, 動作する) => (きれい, 動作する)の3象限をred => green => refactorと回り続けるのがTDDである。
Jack Reevesによれば「プログラミングは組み立てではなく。設計行為であ」り、そこにおいて
だという。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"を実践できる。
t-wadaさんによれば、仮実装 & 三角測量はまわりくどいやりかたに見えるが、その場で挙げたような単純な例ではなく、本当に複雑な実装の場合は効果を発揮するという。この手法が真価を発揮する本物の例はそれ自体が複雑なのでここでは挙げられないが、WEB+DB PRESSの6章を見てほしいとのこと。
複雑な問題を解くには、green barを出すのに時間が掛かってしまう。それを解決するのが仮実装である。そういえば、t-wadaさんの孫弟子ことid:moroさんから聞いたかくたにさんの言葉を思い出す。テストは資産だから、テストを書き上げたら早くそれをソースコード・リポジトリにコミットしたい。けれども、redの状態でコミットするわけにはいかない。そもそも、そのテストが適切なテストかどうかもわからないのに。そこで、仮実装する。仮実装してグリーンになったらコミットする。
テストが資産であるというのはt-wadaさんも再三述べている。
なお、実装イメージがあまりにも明白である場合もある。このような場合は仮実装 & 三角測量を行うには及ばないそうだ。実装イメージが明確に見えている場合はそのステップは抜かし、テストを書いたらすぐに実装を書くというのがt-wadaさんのやりかただそうだ。私の個人的な体験からは、三角測量の練習には明白な実装が見えている対象物を練習台にしたほうが良いのだけれど、いつまでもそんなことをやっていたら効率が悪いのは確かだ。「不安をテストとして行う」というのもt-wada流の教えである。一気に実装できてしまう自信があるならばそうする。不安を感じるならば仮実装を経る。
不安をテストとして行なうと言えば学習テストである。未知のライブラリを使って作業するとき、これで使い方が正しいのかと不安になる。実現すべき機能と、正しいライブラリの使い方と二つを同時に意識するのは難しい。そこで、「使い方はこれで正しいだろうか」という不安をテストにする。自分の理解ではこのライブラリはこう動作するはず、という内容をテストコードとして書き、ライブラリに対してテストを掛ける。
こうして書いたテストはあとあと、そのライブラリに対する格好のコード例となる、ともWEB+DB PRESSに書いてあった。
DBや外部システムなどの、重かったりしてテストの足かせになる部分を仮のオブジェクトで置き換えてしまう。とはいえ、mockはあくまでも「このコンポーネントはこう動作するはず」という自分の妄想の産物なので使いすぎには注意するようにとのこと。資産価値も低いそうだ。
DBにしてもin processの軽量DBがいろいろ出てきたりしているので足かせにならなければ本物を使うに越したことはない。
不安をテストにするt-wada流。はしごはテストの良いアナロジーだそうだ。
小さいテスト、学習テスト、mockは資産価値が低い。メンテナンスコストがもったいない。
テストは資産であるが、テストのメンテナンスにもコストがかかる。粒度の小さいテストは実装の詳細な方針に依存する部分が大きく、リファクタリングするとすぐにredになってしまう。こうしてredになってしまったテストはすでに役目を終えている訳なので、もはや管理しようとせずに捨てるべきだそうだ。
DI以前は単純に実装するとクラスの結合度が高くなり、テストしづらかった。そこでFactoryを使うが、今度はFactoryが「有名人」(そこら中から参照される)、「物知り」(そこら中を参照する、責務が多すぎる)になってしまっていた。
DI以降、テスト時には簡単にモックを差し込めるし、Factoryのような問題は発生しない。DIはテストをしやすくした。
TDDはスキル