Romanticismこの広告は、90日以上更新していないブログに表示しています。TDDデブサミ関西2012での講演内容まとめはじめに今月、GOOS日本語版が発売されました。見る継続的デリバリーに続き、高木さんと一緒にお仕事をするのはこれで二冊目です。今回も多くの人に助けられて、目標としていたデブサミ関西での出版にこぎつけることができました。関係者の皆さま、どうもありがとうございました。講演では触れませんでしたが、ここで「実践テスト駆動開発」というタイトルの由来について少し書いておきます。Tests"で、前半の頭文字をとってGOOSと呼ばれています。"grow"も"guide"も本書にとって外すことのできないキーワードでしたので*1、タイトルから外すことは考えられませんでした。これをうまく収めるタイトルがどうしても浮かばなかったため、直訳を副題としました。(Beck)))以来、TDDにとっては二冊目のバイブルという位置づけを大切にしようと考えたのです。講演のタイトルを「テスト駆動開発の進化」としたのは、2002年のケント・ベックの著作と比べてGOOS本はどういう所が変わっているのか、という点にフォーカスしているためです。今回の講演のアジェンダです。テスト駆動開発の進化とは?ケント・ベックの著作と比べて何が変わったのかということを整理します。GOOSのポイント進化という観点だけではとらえきれないGOOSのポイントについてご紹介します。開発の現場で活かすには?GOOSで紹介されるテスト駆動開発を実際のソフトウェア開発のライフサイクルに組み込むにはどうすればいいかを考えます。それでは内容に入っていきましょう*2。当日使ったスライドはこちらです。テスト駆動開発の進化そもそもテスト駆動開発とは、プロダクションコードに先立ってテストコードを書く(テストファースト)ことで、コードを動かしながら完成に近づけていくという手法です。失敗するテストを書き(レッド)、そのテストを通るようにし(グリーン)、先に進む前にリファクタリングする、というリズムは、黄金の回転と呼ばれたりもします(詳しくは(こちら)。具体的にFizzBuzz*3を使って例を示します。TDDでは、プロダクションコードを書く前に、まずはテストコードを書きます*4。}シンプルなテストですが、これだけでも多くの判断をしています。クラス名はFizzBuzzにしようメソッド名はresultにしよう(これでいいの?)戻り値は文字列にしよう("fizz"と"buzz"があるので)この状態では実コードがありませんのでコンパイルも通りません。コンパイルを通しながら、最もシンプルな仮実装をするとこうなります。Integer.toString(i);でも構いません。どこまで先に進んでいいかということについてTDDでは特に何も定めていないのです。重要なのは、「何をテストすべきか」という自分の理解を深めながら先に進むことです。個人的には、まずはできる限りシンプルなテストと実装(この場合であれば「1を渡したら"1"が戻る」)でインターフェイスを定め、その後で仕様に関するテストを計画的に書いていくのが良いのではないかと思っています。テストがグリーンになったところでリファクタリングです。resultというメソッド名がなんとなく気になりますが、今は先に進みましょうか。テストを書いてレッドにします。インターフェイスが定まったので、次は仕様のテストですね。今回は、数字のテストを置いておいて先に"fizz"をやりました。}これを通すためにコードはこうなります。}これでふたつめもグリーンです。中身もとりあえず良さそうですね。して、5の倍数だったら、公倍数だったら、数字だったら、とテストを書いていくと、動かしながら完成品が出来上がります。テストがあるのでリファクタリングを安心してできるところも、TDDのいいところですね。ここまで読んで頂いておわかりの通り、TDDは何かひとつのクラスを実装するには非常に優れた手法です。普段我々が携わっているようなアプリケーション(私の場合はWebアプリケーションになります)では、どこから手を付けて良いのかわからなくなってしまいます。した疑問に対して、スティーブとナットはやはりテスト駆動開発の黄金律を使って立ち向かいます。その黄金律とはすなわち、まずは失敗するテストを書くというものです。そこで彼らが最初に書くテストは、「受け入れテスト」です。受け入れテストでテストするのは、システムが実装すべきフィーチャ、顧客に対して提供すべき価値ということになります。この受け入れテストはエンドツーエンドに実施しなければいけません。エンドツーエンドとは、システムの「端から端まで」、たとえば、ユーザーインターフェイスからデータベースまでということです。この受け入れテストから書き始めるTDDは次に示すような二重のループを描くことになります。外側のテストは実装すべき機能がどこまでできているかを示す進捗の指標となり、内側のテストによってコードの質が高く保たれます。この外側のループと内側のループを繰り返し辿ることで、ソフトウェアが作られていきます。本講演のタイトルにしたテスト駆動開発の進化とはすなわち、コードを書くことから、ソフトウェアを作ることへということなのです。GOOSのポイントさて、前述した二重のループに加え、GOOSにはいくつか特筆すべきポイントがあります。ここではそれを二つとりあげます。それが、モックとウォーキングスケルトンです。モックについてケント・ベックがJUnitの作者であるように、スティーブとナットはjMockの作者です。jMockは二人のオブジェクト指向観をベースに作られています。彼らはオブジェクト指向システムを、「協力しあうオブジェクトの網の目」ととらえます。クラスの継承関係よりも、オブジェクトがどのようにコミュニケーションし合うかが大切だと考える彼らにとっては、クラスよりもインターフェイスが重要です。そのような発想に従い、網の目としてオブジェクトが構成されていると、テストをするのが難しくなってしまいます。あるオブジェクトをテストしようと思うと、隣り合う他のオブジェクトも動いてしまうことになるからです。そこで登場するのがモックです。モックを使うと、隣り合う他のオブジェクトをニセモノと差し替え、テストの際に思い通りの動きをさせることができます。Romanticism」をご紹介しました。興味のある方はご一読くださいませ。前のブログの方には書いていませんので、テストコードの表現がシーケンス図と同じになるという点をここで強調しておきたいと思います*5。is("VALUE"));}例に示した仕様ではオブジェクトローダーが事前に存在することが想定されていましたが、実際にテストを設計しながらインターフェイスが発見されるということも十分に起こります。受け入れテストからはじめて、外側から内側へ、インターフェイスを発見しながら開発を進めていく点がモックを使ったTDDの特徴になります。モックを使ったテストコードを書くことは設計の活動ですので、テストファーストとはいえコードを書くことにこだわりすぎず、色々な図を使いながら考えて進めていくのが良いのではないかと思います。そのようにインターフェイス(あるいはオブジェクト)を発見するためのパターンがGOOSには三つ出てきます。out)あるオブジェクトが責務を持ちすぎている場合、ふるまいの凝集した単位を分割するというものです。off)新しい概念が登場したときにプレースホルダー型をラップする(値)もしくは、サービスを定義する(オブジェクト)というものです。分解は既にあるものを分けるのに対して、発芽は新しい概念が導入される点が特徴です。up)関連するオブジェクトの集団をひとつのオブジェクトにまとめるというものです*6。上記二つは分けるパターンだったのに対して、これはまとめるパターンです。これらのうち、分解と包含は基本的な考え方だと思いますが、発芽はきれいなコードが書けるようになるためのいいテクニックだと思います。一つ重要な点として、ビジネスロジックの中核は、ビジネスドメインの言語を使うべきだ、というものがあります。これについて詳しく知りたい方は、ぜひ以下の本をご覧ください。見るウォーキングスケルトンについてGOOS流のTDDでは、フィーチャの開発とエンドツーエンドテスト基盤の開発を並行して進めなければなりません。これには、テストが失敗したときの原因がどちらにあるかわからないという点を始めとした難しさがあります。Skeleton)とは、これを解決するための方法です*7。できる限り薄いフィーチャのスライスを取り出し、それをビルド/デプロイ/テストするための基盤を作ります。した基盤が一度できれば、あとは少しずつテストを増やし、プロダクションコードを育てていくことができるようになります。詳しくは以下の本をご覧ください。見る中間のまとめこれまでの内容をいったん整理します。第一に、GOOS流TDDの核心は受け入れテストとユニットテストにより構成される二重のループにあります。それを円滑にまわすために、まずは薄いスライス(ウォーキングスケルトン)を使ってテスト基盤を作り、フィーチャを増やしていくという形をとることになります。第二に、テスト駆動開発という名前がついてはいますが、ここで言われているテストはシステムのふるまいです。テストを考えるということは、システムを外側から見たときのふるまいを考えるということに他なりません。Romanticism」をご参照ください。開発の現場に活かすためにさて、ここまでGOOSに書かれたTDDの特徴を見てきました。自社システムの開発を数人で行っているチームであれば、ここまでのノウハウで十分にうまくいくと思います(もちろん、スクラムのようなある程度の開発プロセスに関する方法論は必要でしょうが)。プログラマ30名〜40名で行う受託開発を考えると、なかなかこの形に持ち込むのが難しそうです。最後に、こういった中規模〜大規模の開発現場でGOOSを活かすにはどうすればいいかを考えていきたいと思います。二重のループをどう開始すればよいか、という問いは、二重のループの外側を見ることでしか解決できません。二重のループに入る前の段階として、GOOSには以下のステップが説明されています。問題を理解するアーキテクチャを定めるビルド/デプロイ/テストを自動化するここで重要なのは1.と2.、特に2.のアーキテクチャです。GOOSが想定しているアーキテクチャは、タイトルにある通り、「オブジェクト指向ソフトウェア」です。ここで想定しているような中規模〜大規模の開発では、すべてを「オブジェクト指向」で作ることはあまり現実的ではありません。重要なのは、業務分析を行った上でドメイン(ここでは「機能の大分類」と読み替えて頂いても構いません)を抽出し、それぞれのドメインごとに特性を分析することです。ここで分析すべき特性とはすなわち、業務の複雑さをどこで受け止めるべきか、ということです。ここでは二つ分類を出してみます。エンティティ主体のドメインロール主体のドメインエンティティ主体のドメインは、普段多くの方が目にしているものだと思います。業務の複雑さがデータモデルに吸収され、E-R図とSQLがビジネスロジックの中核になるようなドメインです。このようなドメインであれば、ムリにオブジェクト指向的に作る必要はなく、SQLを気持ちよく書くためのSQLテンプレートエンジンを準備した上で、入力チェック、DBアクセス、編集処理といった手続きをトランザクションスクリプトとして実装することが正解となります。このようなドメインに対してO-Rマッパーを当てはめた結果、コード内に大量のJPQLが埋め込まれ結果として可読性が落ちてしまうケースを見たことがある方も多いのではないでしょうか。したドメインの場合、テストとして設計するべきは、シナリオではなく、データのパターンになります。ユーザの操作だけで見ると「詳細画面を表示する」という一シナリオで済んでしまうところ、「このフラグがこうなっている場合はこの項目は表示しない」、あるいは「ここがこういう状態の場合にはリレーションのこっちから値を持ってくる」といったパターンが出てくることになります。一方で、処理が複雑で、ロール間のインタラクションとしてモデル化した方が適切だと思われるドメインも中には存在します。例としては、決済処理や権限といったよくある共通処理、あるいは外貨金利の日またぎ計算といった特殊な業務ロジックが挙げられます。このようなドメインに対してトランザクションスクリプトで立ち向かってしまうと、処理が重複したり、メソッドが長くなりすぎたり、という問題が発生します。テストの設計も、「こういう条件でこういう入力があったらこのインターフェイスを呼び出す」という形のシナリオになるでしょう。ここでは二つの分類を出しましたがこれが二つである必然性はありません。各ドメインを記述するのに適切なパラダイムは他にもあり得ると思います。重要なのは、作るものの特性を見極め、それに合わせたアーキテクチャを策定しようということです。GOOSではソースコードは「育てる」ものだとされていますが、そのソースコードが育つ方向性を事前に規定するのがアーキテクチャなのです。ご協力ありがとうございました。*5:これは行きの新幹線の中で、上司の@yusuke_arclampから指摘されて気付きました。静的な構造を表現するクラス図より、動的な動きを表現するシーケンス図の方が、モックとの相性が良いのです*6:日本語版p.64で包*括*となっていますが、ミスによる表記揺れです。*7:直訳すれば歩くスケルトンで、骨格だけで筋肉のないガイコツが動いている、というイメージです。~『スモール・リーダーシップ』出版のお知らせ~今年読んだ本と仕事に役立つおすすめ5冊(2016年版)GxPで学んだ、ただ一つのことパラダイムを学ぶことと、現実の中で活かすこと月別アーカイブはてなブログをはじめよう!digitalsoulさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。