祝・増刷『JUnit実践入門』 #junitbook
お陰様でJUnit実践入門の増刷が決まりました!
タイトルなどに「Java」というキーワードが含まれず、一部の書店では不遇な扱いを受けていますが、それなりに売れているということでありがたく思います。増刷分は年末にあがってくる予定です。もし、書店などで注文され、入荷待ちの方がいましたらば、もうしばらくお待ちください。
また、誤植情報などがありましたらば、(可能であれば)本日中に著者宛に教えて頂ければ、増刷分には反映できると思います。ちょっとした誤字脱字でも構いませんので、見つけたならば教えてください。
報告は、こちらのブログにコメントいただくか、Twitterで @shuji_w6e宛または、 #junitbook のハッシュタグでお願いします。Facebookでも構いません。
軽量なテスト駆動開発を目指して #TddAdventJp
これは、TDD Advent Calendar jp:2012 の16日目のエントリーです。前日のエントリーは、@pocketberserkerさんの「Specs2のParameterized Testのはなし」でした。
ご存じの方も多くなっていると思いますが、「テスト駆動開発(以下、TDD)」とはテストコードを先に書くテストファーストを基盤とした開発手法です。先にテストコードを書く事により、これからどのようなプロダクションコードを書こうとしているかを明確にすることができることが特徴です。このため、テストの技法というようりは設計の技法です。
テスト駆動開発を実践することにより多くのメリットを得ることができます。このことは2011年のAdvent Calendarで言及しました(TDDを学ぶべき10の理由 #TddAdventJp)。TDDは簡単に導入することができる一方で、実践するのは非常に難しいスキルです。特に簡単な計算機などのTDDはできたとしても、その後にちょっと複雑な、そう自動販売機アプリケーションのようなプログラムをTDDで開発する所に壁がある気がします。
そこで、今年のエントリーでは「軽量なテスト駆動開発を目指して」と題して、そんなTDDのやり方をなんとなく習得した人が、TDDを実践していく上でのヒントとなればと思います。自分がテスト駆動開発を行う時にどんな考えで最初のテストコードを書いているかをまとめたので、参考になれば幸いです。
TDDが難しいワケ
TDDで難しいワケとして、どのようにクラスを分割し、どの程度テストすれば良いかの判断が難しいことがあります。これに対するひとつの回答は「不安がなくなるまで行う」ですが、不安は簡単になくなるものではありません。
そして、TDDを習得することによって、より不安になるというジレンマがあります。特にある程度大きな機能を分割し、TDDを行っていくと、「もう少しテストを追加しないと...」と不安となり、過剰にテストを書いてしまいがちです。「本当にこのクラス設計で大丈夫か?」と不安になってしまうと、最初のテストを書くことが不安になり、最初の1歩を踏み出す勇気が持てません。簡単なメソッドでもテストを書かないと怖い…と思ってしまうと、幾らテストを書いても開発は終わりません。
不安を安心に変えるためのテスト駆動開発が、むしろ開発者を不安にさせている、そんなことはないでしょうか?
はじめの不安は何か?
テスト駆動開発の肝は「不安を安心に変換するサイクルとリズム」です。
もし、最初のステップで「このクラスのこのインターフェイスは本当に使われるだろうか?」といった不安があった場合、その状態で「レッドーグリーンーリファクタリング」とTDDのサイクルを回してもその不安は消えません。2回目、3回目のTDDのサイクルを回すことにより「もし、これがまったく使われなかったらどうしよう…」とより不安が大きくなることもあるでしょう。
はじめに解消する不安を選択することが重要です。
いつ、テストをやめるのか?
テスト駆動開発では、TDDのサイクルを回しながら、不安がなくなった時点でサイクルを止め、次の機能の実装などに移ります。
よくあるケーストして、例外のテストをどこまでしたらば良いか困ったことはないでしょうか?Javaであれば、パラメータにnullが渡された場合の挙動など、「必要となるか解らないけど、不安だからテストしなきゃ…」となる場合があります。不安なのでテストするは正しいのですが、不安な理由は「良く解らない」からなのです。
どこでテストをやめれば良いかを把握するには、何が不安となっているかを知ることが重要です。
軽量テスト駆動開発とは?
軽量テスト駆動開発とは、行うべきテストを見極め、必要なテストコードしか書かないテスト駆動開発です。当たり前のような話ですが、自分の経験的にもテストを書きすぎてしまう傾向があったため、こんなネーミングにしました。
その肝は、何を不安と考えているかを明確にすることと、不安とならないテストは書かないことのふたつです。
なお、自分の感覚としては最盛期に比べ、テストの数は2/3くらいになった気がします。代わりにテストパターンを増やしたり、網羅的にテストを行えるように工夫したりできる余裕が生まれています。
それでは、簡単にですが自分のアプローチを紹介します。
はじめに外部的振る舞いを確認しよう
はじめに解消する「不安」は、アプリケーションの要件を満たす外部的振る舞いです。
外部的振る舞いは、要件定義や外部設計では(システムの)外部境界とも呼ばれ、内部実装に依存しない、アプリケーションの入出力の仕様です。
例えば、FizzBuzz問題*1であれば、指定された値までの数値または文字列を出力することが外部的振る舞いです。
ここで不安はないと感じるかもしれませんが、自分は文章だけの仕様は安心できません。
具体的な例を考える
外部的振る舞い(仕様)では、システムへの要件は列挙されていますが、具体的な例ではありません。このため、誤解して実装してしまうかもしれません。仕様自体に問題があることもあります。このような問題を回避するには、具体的な値を例として考える事です。
FizzBuzz問題であれば、例えば10を入力した場合に「1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz」と表示されるように、ひとつだけ例を作りましょう。
これでひとつ不安が解消されました。
検証可能にする
外部的振る舞いを例にすることで、不安がひとつ解消されましたが、まだTDDを行う段階ではありません。次に行うことは、例を検証可能な形(=テストコード)にすることです。
FizzBuzz問題では入力値に対する標準出力が外部的振る舞いになります。これを検証する方法はあるかもしれませんが、思いつかないことやプログラミング言語の制約などでできないかもしれません。
重要な事は不安を素早く安心に変える事です。
例えば、Javaで「リストをループして標準出力する」というプログラムであれば不安はないでしょう。つまり、入力値に対し、FizzBuzz問題を満たす文字列のリストが取得できれば、安心して標準出力できます。
ここでは、重要な事をふたつ行っています。それは、問題から「安心な部分や検証が困難な部分を省いていること」と「入力値を検証可能な出力値に変換するインターフェイス」を作っていることです。
最初のテストを書く
これで最初のテストコードを書くことができます。
JUnitでもCucumberでもRSpecでも自由にテストコードを書くことができるでしょう。なぜならば、テスト対象への具体的な入力値と戻り値、そして期待値があるのですから。もし、ここでテストコードを書くことで詰まるようであれば、ユニットテストについて学ぶ必要があります。
テストをグリーンにする前に
ここまで、システムを外側から見てテストコードを書いてきました。ここで実装を書けるよう出れば実装を進めます。書けるかどうかの判断は簡単です。不安に思わず、実装が書けそうであれば実装すれば良いのです。FizzBuzz問題であればここで解決する人が多いと思います。
問題はここで不安に思った場合です。
不安をリストアップする
実装に不安を感じたならば、その不安を言語化してください。
ちょっと無理もありますが、FizzBuzz問題の実装で不安と感じるのは「3と5とその公倍数の判定が不安」とか「それらの場合に正しい文字列を返せるか不安」といったところでしょう。
不安を検証可能にする
不安がリストアップされたならば、具体的な例を作り、不安を軽減した後、それらを検証可能なテストコードにします。FizzBuzz問題であれば「1を与えると1を返す」「2を与えると2を返す」「3を与えるとFizzを返す」メソッドがテストできれば不安はなくなりますね。
レッド−グリーン−リファクタリング
不安がなくなる段階まで落とし込んだならば、今まで学んだようなTDDを行ってください。恐らくは計算機クラスのような簡単なテストだったり、ちょっとだけテストダブルを使えば解決するような簡単なテストになっているはずです。
最初のテストをグリーンにする
幾つかの小さな不安、もしくはその不安の下のもっと小さな不安などをひとつひとつ解決していくことにより、最初のテストをグリーンにする準備が整うはずです。上から考えているため、無駄なコードもないでしょう。もしかしたらば、より良い設計が思いつくかもしれませんが、それでも無駄ではないと思います。
不安でない事はテストしない
このようにTDDのサイクルを繰り返していくと、FizzBuzzのメソッドでは引数にゼロが与えられたり、マイナスが与えられたりしたらどうしよう…と考えるかもしれません。
しかし、少し前に戻って再確認してください。要件にそのようなことは想定されていたでしょうか?もし、解らないならば要件や外部仕様の部分で決めることだと解るはずです。
ここで外部仕様として「1以上の整数を与える」という制約を加えてしまえば、FizzBuzzの内部メソッドではその考慮をしなければならないという不安がなくなります。もちろん、1つ上のシステム境界にとして振る舞うインターフェイスでは考慮しなければなりませんが、少なくとも内部メソッドはゼロ以下の値で呼び出されないと、「安心」できるはずです。
ほら、テストしなくても大丈夫!
ここでTDDのサイクルをやめることができます。
アウトインサイドの開発
これって「アウトインサイドの開発」ってやつでは?と思った方はJUnit実践入門かRSpec Bookを読んだ人だと思います。考え方としては受け入れテストによる振舞駆動開発を起点に、そこからユニットテストによるテスト駆動開発を行う流れであるため同じ考え方です。
しかし、重要なことは、振舞駆動開発かテスト駆動開発かではなく、受け入れテストかユニットテストかでもなく、この流れが、もっとも自然に、効率良く、不安を解消することです。
『JUnit実践入門』正誤表(4) #junitbook
誤植速報その4です。
その1,その2,その3, 技評さんの公式サイトです(おって反映されます)。
P239 リスト13.7 15行目
誤)
return authService.login(authUser) ? "ユーザIDとパスワードを正しく入力してください" : "ようこそ、" + authUser.userId + "さん";
正)
return authService.login(authUser) ? "ようこそ、" + authUser.userId + "さん" : "ユーザIDとパスワードを正しく入力してください";
条件文の後のメッセージが逆さです。もしくは3項演算子の頭に!(否定)をつけてくださいませ。
校正がはじまってからコードを修正すると色々とバグりますね、気をつけてはいたもののorz
P328 Cucumberのシナリオ(コード)
誤)
# language: ja
フィーチャ: 計算機
シナリオ: 加算ができる
前提 計算機が初期状態
もし 3と4を加算したならば
ならば 7を出力すべき
正)
# language: ja フィーチャ: 計算機 シナリオ: 加算ができる 前提 計算機が初期状態 もし 3と4を加算した ならば 7を出力すべき
ならばが重複しているのでシナリオが変な文章になっています(これによる副作用などはありません)。
ご迷惑をおかけして申し訳ありません。
(5)に続く...
『JUnit実践入門』正誤表(3) #junitbook
『JUnit実践入門』正誤表(2) #junitbook
誤植速報その2です。
その1はこちら、技評さんの公式サイトはこちらです(おって反映されると思います)。
P196 リスト11.29 部分的なモックオブジェクト
誤)
List list = new ArrayList(); List spy = spy(list); when(mock.size()).thenReturn(100); spy.add("Hello");
正)
List list = new ArrayList(); List spy = spy(list); when(spy.size()).thenReturn(100); spy.add("Hello");
コードの3行目、whenの内部がmockとなっていました。
ご迷惑をおかけして申し訳ありません。
(3)を書かないことを祈ります...
『JUnit実践入門』正誤表(1) #junitbook
11/21発売のJUnit実践入門ですが、都内の大型書店などでは先行入荷しており、続々と入手報告をいただいております。
しかしながら、早速誤植が2カ所発見されてしましました。
P186 リスト11.18
誤)
public class UserDaoStub implements UserDao { @Override public User find(String userId) throws { new UserNotFoundException("connection error"); } }
正)
public class UserDaoStub implements UserDao { @Override public User find(String userId) throws UserNotFoundException { throw new UserNotFoundException("connection error"); } }
throws句の例外クラス指定とthrowが抜けています。
見本誌が到着しました(技評さんに) #junitbook
JUnit実践入門の見本誌が到着したようです。
筆者は札幌に住んでいるので、届くのに2日ほど待たなければなりませんが、都内の本屋さんでは少しずつ発売されるようです。Amazonでは来週になるのかな?
JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)
- 作者: 渡辺修司
- 出版社/メーカー: 技術評論社
- 発売日: 2012/11/21
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 273回
- この商品を含むブログ (69件) を見る
また、今回の書籍に合わせてチートシートを作成しました。書籍にもついていますが、壁紙や印刷で使えるようにPDFと画像ファイルも用意しています。技評さんの公式サイトからダウンロードできるようになりましたので、合わせてよろしくお願いします。
それではお手元に届くまでもう少しお待ちください!
11.15 追記
Elipseのチートシートに誤記がありましたので差し替えています(書籍の方は問題ありません)。