2013年6月3日月曜日

テストケースをグループ化 Enclosedアノテーション

株式会社ジェニシス 技術開発事業部の遠藤 太志郎(Tacy)です。

現在は「テスト自動化シリーズ」と題してJUnitについて紹介中です。
 さて、今までの私の記事に「コードを綺麗に」「効率的に」「コピペしない」などの文言が随所に出てきていることで、薄々感づいていらっしゃるかもしれません。
JUnit開発経験がある方なら、多くの方が身に覚えがあるでしょう。

JUnit開発は、ソースを綺麗に書くことの戦いである。

JUnit開発は、それ自体は決して難しいものではありません。しかし煩雑な作業ではあるのです。
 しかもテスト系ですので、メインソースと比べて重要度が低く、品質管理が甘くなりがち。
この結果、JUnitソースはどうしても可読性の低いものになりやすいのです。

今回の記事は、そんな問題に対して役に立つ便利機能をご紹介しましょう。
それが、テストソースのグループ化機能「@RunWith(Enclosed.class)」です。

実例紹介 @RunWith(Enclosed.class)

JUnitのテストケースの数が増えてくると、「テストケースを性質毎に分別して、グループ化出来ないかな?」と思うようになります。
どのように分類するかはプログラマーの裁量次第ですが、例としては以下です。

正常/異常によるグループ分け

  • 処理が最後まで正常完了するテスト
  • 処理が途中でエラーになるテスト

DBのセットアップによるグループ分け

  • DBが空の状態からのテスト
  • DBにレコードが1件だけある状態からのテスト
  • DBにレコードが大量に登録された状態からのテスト

一つのクラスにテストケースを全部並列にズラッと書いた場合、「あのテストケースはどこに書いたっけ?」と探そうと思っても、パッと見で似たようなソースばかりなので視線が滑り、目的のソースが見つからないということになりがちです。
しかし、こうしてグループ化することで見易く整理されます。
では、実例をお見せしましょう。

/**
 * @author 技術開発事業部 遠藤 太志郎
 *
 */
@RunWith(Enclosed.class)
public class EnclosedSampleTest {

 public static class テーブルが空の状態から始まるテスト {

  /**
   * テーブルを初期状態にセットアップする。
   */
  @Before
  public void setUp()  {
   //テーブルを空にする処理
  }

  @Test
  public void test_レコードの新規登録_最小() {
   //テストコードを書く
  }

  @Test
  public void test_レコードの新規登録_フル桁() {
   //テストコードを書く
  }

 }

 public static class テーブルにレコードが登録状態からのテスト {

  /**
   * テーブルを初期状態にセットアップする。
   */
  @Before
  public void setUp()  {
   //テーブルにレコードをセットアップする処理
  }

  @Test
  public void test_レコードの更新_主キーで更新() {
   //テストコードを書く
  }

  @Test
  public void test_レコードの更新_フラグが一致するものを更新() {
   //テストコードを書く
  }

 }

}

EnclosedSampleTestクラスに「@RunWith(Enclosed.class)」というアノテーションが付与されています。
このアノテーションが「EnclosedSampleTestはJUnitテストソース本体ではなく、複数のテストクラスを取りまとめ役のクラスである。」という目印となるわけです。
そして、テストソース本体は配下のローカルクラスが担当します。
EnclosedSampleTestクラスにペタペタと全テストケースを書いてしまうよりも分類分けがされたのでスッキリしましたね。

(余談ですが、Javaはクラス名、メソッド名、変数に日本語が使えます。本体ソースでは普通こんなことはしませんが、テストソースならば日本語で書いた方が分かりやすくなるのでオススメです)

さて、上記のサンプルソース。
実は単に見やすくなっただけではなくて、もう一つ、非常に重要な機能が隠されています。

それは、「@Before」が2回登場していることです。
このアノテーションは似たような機能がJUnit3の頃からある有名なものですので、特に説明するまでも無いでしょう。

  • @BeforeClass:テストクラスを実行する最初に一回だけ呼ばれる。
  • @Before:全てのテストメソッドを実行する前に毎回呼ばれる。
  • @After:全てのテストメソッドを実行した前に毎回呼ばれる。
  • @AfterClass:テストクラスを実行した最後に一回だけ呼ばれる。

この4種のアノテーションは、その性質上、1テストクラスにつき1つしか書くことが出来ません。

このため、「こっちのテストはテーブルが空の状態からスタートしたい」「こっちのテストはテーブルにレコードを入れた状態からスタートしたい」と、初期セットアップ条件が複数存在するテストクラスの場合、@Beforeメソッド1つではセットアップに対応出来ないのです。

この結果、「@Beforeの機能は使わず、全てのテストメソッドの頭の部分にそれぞれ初期セットアップ処理をベタ書きする」という実に愚直なやり方に走るプロジェクトが世の中に存在してしまうのです。

しかし、そんな愚直なやり方をしてしまうと、セットアップ処理とテスト本体が同じメソッドの中に書かれていることになりますので、可読性が下がってしまいます。

セットアップ処理とテスト本体を分離する。

これはJUnitソースの可読性を高める為の基本中の基本です。
この要望に対応出来る便利な機能、それが「Enclosedアノテーション」なのです。

終わりに

今回紹介した「@RunWith(Enclosed.class)」は、そんな派手な機能ではなく、テストソースを綺麗にするために便利なテクニックといった所でしょう。

次回も再び、テストケースをスッキリするための便利なテクニックを紹介したいと思います。

2 件のコメント:

  1. アサーションじゃなくアノテーションでは?

    返信削除
  2. その通りですね。ご指摘ありがとうございました。
    一先ずこの記事は修正しましたが、他のページにも誤字があるかも……。
    発見次第修正します。

    返信削除