JUnitは単体テストを簡単に書くことができるテスティングフレームワークです。
JUnitテストを行うメリットは開発のスピードアップです。 多くの人はテストを書くことによって開発のスピードが減速すると考えているのではないでしょうか。 そんなことはありません!
超簡易版ECサイトを作ることを例にして考えます。
商品は3つありTOPページに3つの商品が表示されていて、ユーザはプルダウンからそれぞれの個数を選択して購入確認ボタン押下します。
購入ボタンを押下すると、ページ遷移し合計金額が表示される。
ただし、、購入する製品の組み合わせで割引率が変わるため、合計金額の計算はややこしい。
(商品1つだけを買った場合割引なし、AとBのセットの場合:合計金額から5%off、BとCのセットの場合:合計金額から6%off、
CとAのセットの場合:合計金額から8%off、3つセットなら10%off)
というシステムを作ります。
このシステムを作り終えたあと、あなたはどうやって動作確認をしますか?
もし、コードを書いたら即ブラウザを使って画面を表示し、様々なパターンのケースをテストし、
上手くいっていなければ、またコードを書いてテストという方法をとっているのであればそれは時間の無駄です。
JUnitテストを使いましょう。
このシステムをMVCアーキテクチャで考えるとModelは商品、Viewはウェブページ、Controlerは金額を計算して返すActionとなります。
Modelはprivateなfieldとgetter,setterしか持たないDtoとなるはずです。
大人の事情がない限り、IDEにより自動生成されたgetter,setterはテストする必要はありません。
間違っても手打ちでgetter,setterを作らないようにしましょう。
手打ちで作成した場合は間違って他のfieldの値を返しているなど不具合の可能性があるのでテストが必要です。
Viewは基本的に単体テストではテストすることが難しいためテストしません。
後の結合テストでテストします。
Controler(Action)はHttpRequestを受け取りHttpResponceを返しますが、これをJUnitテストで確認するのは面倒です。
テストを行いやすいように金額を計算する部分をLogicに切り出しましょう。
そしてLogicに対する実装とテストを書いていきます。
実装とテストをどちらを先に書くかは、思想によるので、好きな方でいいと思います。
私はこのテストでは以下のケースを書きます。
・商品を買わないケース
・Aを1つだけ買ったケース
・AとBを1つずつ買ったケース
・BとCを1つずつ買ったケース
・CとAを1つずつ買ったケース
・全部1つずつ買ったケース
・全部最大個数買ったケース
ちゃんとユーザの入力値をValidationする設計を行っていれば以下のケースは必要ありません。
・1つでも最大個数以上買ったケース
・1つでも負の個数買ったケース
etc.
すると一回テストを書いてしまえば、あとはJUnitテストを流してグリーンになるまで実装を直すだけなので、とても楽です。
単体テストでブラウザやアプリを立ち上げる必要はありません。
「俺は一発でテスト通せるからテスト書くより、ブラウザで目視確認する方が楽なんだけど・・・」
という人がちらほらいるようです。
しかし、単体テストの真価は仕様変更やリファクタリングの際に発揮します。
ブラウザでテストしている人は、割引率が変わったらまた全ケースブラウザで叩かないといけません。
JUnitテストをきちんと書いている人は実装とテストを同様にいじるだけですぐに終わります。
リファクタリングの際はリファクタリングが終わるとJUnitテストをただ流すだけでちゃんとリファクタリングできているかが確認できます。
できるだけ単体テストを行いやすいようにLogicに切り出す方が単体テストは楽ですが、
どうしても切り出しが行えず、複雑な処理をテストで追う必要がある場合があります。
そんな時MockObjectを作成できるAPIは非常に便利です。
私はMockitoを使っています。
public int xxx(YYY yyy) {
if(yyy.isStarted()){
return 1;
} else{
return 0;
}
}
この時YYY#isStarted()はfieldを返すgetterではなく複雑な処理で起動しているのか調べるものなのでYYY#setStarted()はないものとします。 (コーディング規約違反になる機関も多そうですが・・・)
private final static XXX TARGET = new XXX();
@Test
public void xxxTest_YYYが起動している場合1を返す() {
YYY yyy = mock(YYY.class);
when(yyy.isStarted()).thenReturn(true);
assertThat(TARGET.xxx(yyy),is(1);
}
このようにMockを使うことでテストは楽にかけます。
この一見意味のないようなテストも書いておくことにより、リファクタリングの際のミスを減らすことができます。
少し話は変わりますが、カバレッジを100%にするということはすべての分岐を行ったことが確認できただけで、
その分岐内で行われている処理自体が正しいかどうかまでは確認できません。
ちゃんと正しいassertが行われていることも確認してください。
逆にカバレッジ100%にするために業務上絶対発生しないExceptionをスタブを用いて無理やり発生させるなんてテストを書くのは時間の無駄なのでやめましょう。
・JUnitテストは書くのがめんどくさいと感じるが、結果的に時間短縮になる。
・JUnitを書くのに便利なAPIはあるけど、できるだけ使わずにテストできるような切り出しを行う。
・カバレッジだけでは品質を保証できない。