メモ: JUnitのexpectedフィールドとRuleアノテーションで例外のテスト
次の記事を読んで、初めて知ったのでメモ。
- Testing Custom Exceptions w/ JUnit's ExpectedException and @Rule
このようなテスト対象のコードがあるとします。
import java.util.ArrayList; import java.util.List; public class Deck { /** * 指定した人数のプレイヤーに、山札を均一に配る。均一に配れない場合、プレイヤー間の配布枚数の差異は1枚以内とする。 */ public List<List<Card>> divideCards(int playerNum) { if (playerNum < 2) { throw new IllegalArgumentException("プレイヤー数は2以上でなければならない。"); } List<List<Card>> result = new ArrayList<List<Card>>(playerNum); // 略 return result; } } class Card { // 略 }
divideCardsメソッドは、山札をプレイヤーに配ります。ここで、ゲーム中のプレイヤーが、2人以上であることを想定しています。もし1人以下のプレイヤーに対して山札を配ろうとすると、例外IllegalArgumentExceptionが発生します。
このコードのテストは、これまでこんな感じで書いてきました。
import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; import org.junit.Test; public class DeckTest { Deck deck; @Test public void testDivideCardsForException() throws Exception { deck = new Deck(); try { deck.divideCards(0); fail("ここには来ない"); } catch (IllegalArgumentException expected) { assertEquals("例外のメッセージ確認", expected.getMessage(), "プレイヤー数は2以上でなければならない。"); } } }
しかし、今はこんなふうに簡潔に書けるのですね。
import org.junit.Test; public class DeckTest { Deck deck; @Test(expected = IllegalArgumentException.class) public void testDivideCardsForException() throws Exception { deck = new Deck(); deck.divideCards(0); } }
Testアノテーションのexpectedフィールドに、実行時に発生する例外クラスを指定するのがポイントです。
しかし、この書き方には欠点もあるようです。
たとえば、さっきと少し違って、テスト対象のコードがこんな感じだとします。
import java.util.ArrayList; import java.util.List; public class Deck { /** * 指定した人数のプレイヤーに、山札を均一に配る。均一に配れない場合、プレイヤー間の配布枚数の差異は1枚以内とする。 */ public List<List<Card>> divideCards(int cardNum, int playerNum) { if (cardNum < 1) { throw new IllegalArgumentException("山札は1枚以上なければならない。"); } if (playerNum < 2) { throw new IllegalArgumentException("プレイヤー数は2以上でなければならない。"); } List<List<Card>> result = new ArrayList<List<Card>>(playerNum); // 略 return result; } } class Card { // 略 }
上のコードでは、divideCardsメソッドの引数が2つになっていて、IllegalArgumentExceptionを投げる原因が、「1つ目の引数が不正なとき」と「2つ目の引数が不正なとき」の2パターンに増えています。(もちろん、テストケースの考え方によっては、「両方誤っているとき」も加わりますが、それはさておき)
expectedフィールドを使う書き方では、テスト対象コードに例外を投げる原因が複数あるとき、それぞれの原因を識別できません。IllegalArgumentExceptionが飛んできたことだけしか検出できないのですね。
そういうときは、こんなふうに書けばよいそうです。
import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class DeckTest { Deck deck; @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void testDivideCardsForDeckException() throws Exception { deck = new Deck(); thrown.expect(IllegalArgumentException.class); thrown.expectMessage("山札は1枚以上なければならない。"); deck.divideCards(0, 2); } @Test public void testDivideCardsForPlayerException() throws Exception { deck = new Deck(); thrown.expect(IllegalArgumentException.class); thrown.expectMessage("プレイヤー数は2以上でなければならない。"); deck.divideCards(2, 0); } }
Ruleアノテーションをつけた、ExpectedException型のフィールド変数がポイントです。この変数に例外の型やメッセージを設定して、テストを実行すると、検証を行えます。
メモは以上です。
(2012/10/3 追記)1つのメソッドに、テスト対象コード呼び出しを2回書いていましたが、分けました。1回目の呼び出ししか実行されないため、間違っていました。shuji_w6eさん、backpaper0さん、ご指摘ありがとうございます。