実践テスト駆動開発を読んだ(和智さんいい仕事、ありがとう!)。
タイトル(GOOS = "Growing Object-Oriented Software, Guided By Tests")に、「テスト(TDD)」と「オブジェクト指向(Object-Oriented)」と「育てる(Growing)」が入っていて、ずっと読まなきゃと思っていた。出たときに角谷さんに「これは!」、と薦められたのに、機会を失っていたけど、最近、astahの開発でテストに悩みがあって読んでみた。
外から攻めるか、内からか
テストを書いてプロダクトコードを育てていくという話なのだが、内側のテスト(ユニットテスト)と外側のシステムテスト(受け入れテスト、システムテスト、エンド・トゥ・エンド(E2E)テスト)をどっちを先に書くべきかいつも悩む。外側のテストを書いて、内側に進んでいくのか、内側から組み上げるか。設計の方向とテストの方向。
ぼくはある程度ラフにモデリングをして、内側から組んでいくのが好き。
┏━━━━━━━━━━━┓
┃ユーザインターフェイス┃
┃ ┏━━━━━━━┓ ┃
┃ ┃ ┏━━━┓ ┃ ┃
┃ ┃ ┃モデル┃⇒。。。⇒ 中から外へ向かって設計
┃ ┃ ┗━━━┛ ┃ ┃
┃ ┗━━━━━━━┛ ┃
┃ ┃
┗━━━━━━━━━━━┛
Meyer の Inside-Outだ(昔書いた記事「ソフトウェア原則[2] - IOP(Inside-Out Principle)」)。ただ、ある程度アーキテクチャが決まっていたり、既にコードがたくさん書かれていたり、フレームワークによって強制される場合は外側からも可能なのかもしれない。まったくコードがないところからOutside-In で進んで、アーキテクチャの創発を期待する勇気は難しなぁ。と正直今でも思っていた(いる)。このあたりを少し、整理も含めて書いてみたい。
この本では、
新しいドメインモデルのユニットテストから始め、それをアプリケーション内の残りの部分に紐づけるという発想は魅力的だ。その方が簡単に思える。(中略)。。。しかし、このやり方では後になって統合の問題に苦しめられることになるだろう。
とし、クラスレベルのテストファーストだけでなく、受け入れテストを含めて、テストファーストを推奨している。アーキテクチャとTDDに関しては、Cope vs Uncle Bobの議論 などもあるし、(ちょっと話題違うけど)特定フレームワークを使った場合のTDDの向き不向きについては最近の DHH/Fowler/KB のIs TDD Dead? の議論もある。けど、確かに、
- 詳しいE2Eテストを書いてしまうのことができるかどうか
- クラスレベルの設計をしてから進むか、コードの声を聞きながらTDDと創発に頼って設計するか
という観点は別観点として、とにかく「受け入れテストを先に書く」ことはできるだろう。(ちなみに、この本では「受け入れテスト」と「システムテスト」と「進捗を測るテスト」を区別している。(後述))
「アダプターとポート」パターン
ということで、テストの種類と順番である。どっちから攻めるか(方向)とどんな意図でテストを使うか。このあたりが、本書の白眉だと思う。方向を説明するには、参照する地図がいる。この地図としてこの本が借りてきているのが、「アダプターとポート」パターンだ。
これはもともと、Alistair Cockburn が「ヘキサゴンアーキテクチャ」と呼んでいたもの。
- コーバーンのポートとアダプターパターン
本書が書かれてから後に、著者のNat Pryceが自分のブログ: Misteaks I Hav Madeにより詳しくこのパターンとテストの種類の違いを書いている。以降の絵はそのブログのエントリ: Visualizing Test Terminologyから引用する。
これが、Nat によって書かれた、本の中の「ポートとアダプタ」パターンの絵である。
- Nat が描いた「ポートとアダプタパターン」
真ん中に「アプリケーションコア」(ドメインモデルを含む)があり、外側にUIやテクノロジ(Web、永続化)がある。アプリケーションコアはこれら技術詳細の知識を持たず、ポートを通して、外界とインターフェイスする。「アダプタ」が両者を意味インピーダンスを変換し、伝える。
典型的なWebアプリでは、ポートは「UI」と「DB」の2方向であり、このモデルでは**両方が「外側」**である。よって「エンドトゥエンド」ということは、外のポートから入ってきて中に入り、さらに外のポートに出て行くことになる。
さて、テストの意味と範囲
このブログからそれぞれのテストの意味と範囲を説明しよう。
ユニットテスト
ユニットテストは個々のオブジェクト、および値(バリュータイプ)をテストする。
インテグレーションテスト
「自分たちのコード」と「他人が作ったコード」を合わせてテストする。うまく「インテグレート」されたかどうか。(この例ではDBとのインテグレーション)
受け入れテスト
ドメインロジックがちゃんと出来ているかどうか、「顧客に見せる」テスト。ここで面白いのが、「UI」や「DB」をすっとばしてもいい、と言っている。ドメインロジックが正しいことを見せるのだから。この絵では、アプリケーションコア(ドメインモデル)と、その処理結果を、他の技術層から切り離し、そこを顧客に見せるテストとして表現している。(これを受け入れテストと呼ぶのは、ぼくには違和感がある。ただ、DDDの文脈で顧客とドメインモデルを育てる、という話ならしっくりくる)
システムテスト
これが、システムすべてを通過する、E2E(エンドトゥエンド)テストのこと。本書では、これを最初に確立して「TDDに火を入れよ」と言っている。
過去から現代へのTDDの意味づけ
さて、Meyer からはじまって GOOS、Ports and Adapter、そして Nat のブログの中の解説を試みた。
ということでこの歴史と本書の役割について t-wada がすぱっと言っていたので引用しておきます。これでこの記事を締めたいと思う。
@t_wada さんのインタビュー(2012 http://devtesting.jp/pekema/?0003/Hotlinks) より引用、
例えば受け入れテストとか、いわゆる MVC でいうとコントローラやビューから作って、内側をモックにして、要求の方から作っていって中に攻めていく方針が OUTSIDE IN というテスト戦略。 そうじゃなくて、抽象的な部品とかモデルレイヤから作っていってボトムアップで攻めていくのが、INSIDE OUT 戦略。....Kent Beck や Fowler がやってた 2000 年ぐらいの TDD の姿と、その後の Steve Freeman とか Nat Pryce とか goos の人たちがやりだした TDD との大きな違いというのは、「向き」が出てきたということですね。....Steve Freeman と Nat Pryce は、モックオブジェクトを使うことで、テスト駆動開発自体は、開発する要求や対象の世界を自分たちが理解していく上での設計ツールになり得るし、設計の時間を制御できることに気づいたんですね。 ...オブジェクト指向言語とモックオブジェクト (テスト用の偽物) という道具を組み合わせてテストに使うことで、決まっているところはともかく、決まっていないところでも実装のことを考えずに外側から決めていくことができるようになったことに、テスト駆動開発をやっている一部の人たちが気がついたんです。 それが OUTSIDE IN 戦略。システムに対してテストする方向もそうだし、時間でもあるんですね。 外側から先に作っていくという話なんです。学びと時期をテストで制御する。
おしまい。
(平鍋)