JUnit4を利用したJavaのテスト概要
概要
JUnitを利用した、Javaのユニットテストを作成する際の基本的な部分について記載しました。
JUnit実践入門の内容 + 経験から記載しました。
テストコード基本
ユニットテスト対象物
テストクラスに対する、テスト対象物はテストクラスのコードのみとします。
そのため、外部オブジェクトの作成等は基本的にモック化します。
(外部クラスのコードのテストは、外部クラスのテスト上で行えば良いとの考え方です。)
またテストメソッドの対象物ですが、基本的に publicメソッド
を対象とします。
privateメソッド
は、publicメソッド
を正確にテストすることで、同時にテストされていなければ、ならないと考えているためです。
各命名と役割
命名 | 役割 | 備考 |
---|---|---|
XXXTest | テストクラス名 | |
xxx_条件_結果 | テストメソッド | - xxx()メソッド - @Test アノテーションを付ける |
sut | テスト対象オブジェクト変数名 | - テスト対象のオブジェクトを明確化するために同一名称で宣言する。 - System Under Testの略 |
setUp() | テストごとの前処理 | - 各テスト実行ごとの前処理を記載 - @Before アノテーションを付ける |
tearDown() | テストごとの後処理 | - 各テスト実行ごとの後処理を記載 - @After アノテーションを付ける |
setUpClass() | テストクラスごとの前処理 | - テストクラス内のテストが一つでも実行される前の処理を記載 - @BeforeClass アノテーション記載 - public static methodで宣言 |
tearDownClass() | テストクラスごとの後処理 | - テストクラス内のテストが全て実行された後の処理を記載 - @AfterClass アノテーション記載 - public static methodで宣言 |
テストのフェーズ
事前準備 =
set up
下記を行います。- テストデータ作成
- DBの接続
- モックの作成
- モックからの返り値の設定
- Testクラスに渡すオブジェクトの作成
- Testクラスのオブジェクト化
- ..etc
実行 =
exercise
テスト対象のメソッドを実行します検証 =
verify
メソッドの実行結果を検証します。
メソッドの返り値が期待値通りかを判定するフェーズです。後処理 =
tear down
テスト実行後の後処理を行います。- テストデータの破棄
- DBの切断
Assert
テスト結果を検証するための方法についてです。
検証とは基本的に、テスト結果が期待値通りかを確かめるフェーズとなります。
- Assertは、
org.junit.Assert.assertThat()
を利用- `assertThat` 一つで基本的に事足りる - static importを行っておく
- Matcher
org.hamcrest.CoreMatchers
is()
equalsメソッドによる比較nullValue()
nullであることを検証する- 利用する際は、
is(nullValue())
と利用する
- 利用する際は、
not()
評価値を反転させる
org.junit.matchers.JUnitMatchers
hasItem()
Iterableインタフェースを実装したクラスに、期待値が含まれているか検証hasItems()
複数指定可能
BaseMatcher<>
を継承することで、カスタムMatcherが作成できる
サンプルコード
上記までの内容を踏まえた、テストサンプルコードです。
public class TargetClassTest { @Before public void setUp() { // テストごとの共通の前(初期化)処理 } @After public void tearDown() { // テストごとの共通の後処理 } @Test public void hasUserName_ユーザー名称をもつ場合_trueを返却() throws Exception { // set up /** テスト対象のオブジェクトの変数名は sut とする **/ TargetClass sut = new TargetClass)(); // exercise boolean actual = sut.hasUserName(); // verify /** 英語の構文 "assert that actual is expected" を意識する**/ asseertThat(actual, is(true)); }
(追記) Exception発生時のテスト
/** * Exception発生時のテスト */ @Test(expected = NullPointerException.class) public void fetchUserName_ユーザー名称を取得に失敗_exception発生() { // set up /** テスト対象のオブジェクトの変数名は sut とする **/ TargetClass sut = new TargetClass)(); // exercise /** 下記メソッド実行時にException発生 **/ Result actual = sut.fetchUserName(); } }
テスト作成にあたっての他用語
Fixture
事前準備にて、設定する情報のことで、下記2パターンのFixtureの設定方法があります。
- inline set up
- 各テストメソッドごとに、fixtureのset upを行う
- simpleに設定を記述すれば良い
- コードが長くなり、可読性が悪くなりがち
- implicit set up
- @Beforeアノテーションをつけたメソッドにて設定を行う手法
パラメータ化テスト
テストに対してパラメータを設定したい際に、利用します。
Theories
を利用して実現します。
- Theories
@RunWith(Theories.class)
をクラス宣言の前に宣言- テストランナーの一つ
@Theory
テストメソッドのアノテーション@DataPoint
パラメータ
// サンプルコード @RunWith(Theories.class) public class TargetClassTest { @DataPoint public static int PARAM_1 = 1; @DataPoint public static int PARAM_2 = 2; public TargetClassTest() { // 初期化処理 } @Theory public void testCase(int x) throws Exception { } }
Rule
テストをプラグイン的に拡張できる機能のことです。
テストに関係なく、初期化+後処理をしたい場合 に便利です。
publicなfiledにアノテーションをつけて利用します。
// サンプル @Rule public Timeout timeout = new Timeout(100);
- 処理はテストごとに実行される
→ テストごとに実行したい共通処理をRuleとしてまとめられる @ClassRule
にてテストClassごとに1回のRuleも作成可能- カスタムルールの作成 方法1
org.junit.rules.TestRule
インターフェイスを継承するStatement apply(final Statement base, Description description)
をOverrideする- 引数で渡される
Statement base
が各テストメソッドのイメージ base.evaluate()
を実行することでテストが実行される
- 引数で渡される
- これを利用することでテストに共通の前後処理を定義することができる
public class HogeRule implements TestRule { private void before() {} private void after() {} @Override public Statement apply(final Statement base, Description description) { // new Statement()することで実際のテストを拡張している return new Statement() { // 前処理 before() // テスト実行 (@Before -> テスト実行 -> @After) base.evaluate(); // 後処理 after() } } }
- カスタムルールの作成 方法2 (← こちらのほうが効率よく作れます)
- 上記の設定を、事前に行っているクラス(
ExternalResource
)を利用 - ExternalResourceを継承して作成
- 利用する際は、
before()
とafter()
をOverrideして、前後処理を記載する - Rule内で利用する変数はprivate fieldに宣言しておき、コンストラクタで受け取る
- 上記の設定を、事前に行っているクラス(
// 上記のサンプルコードをすでに実装した抽象クラス public abstract class ExternalResource implements TestRule { public Statement apply(Statement base, Description description) { return statement(base); } private Statement statement(final Statement base) { return new Statement() { @Override public void evaluate() throws Throwable { before(); try { base.evaluate(); } finally { after(); } } }; } /** * Override to set up your specific external resource. * * @throws if setup fails (which will disable {@code after} */ protected void before() throws Throwable { // do nothing } /** * Override to tear down your specific external resource. */ protected void after() { // do nothing } }
モック
モックとは、テスト対象クラス以外のクラスを擬似的に作成して、対象外クラスのメソッドの
返り値を設定する事ができます。
テスト対象クラス に絞ったテストを行うために重要な要素です。
org.mockito.Mockito
ライブラリを利用する- JavaDoc
- mockの利用方法について詳細が記載されている
- モックの作成
mock(TargetClass.class)
- 返り値の設定
when(sut.exec(anyInt(), any(Date.class))).thenReturn(obj)
- JavaDoc
データベースのテスト
(手法が確立できていないため一時保留)
- H2 Databaseの利用
- DBUnit
- @Ruleとして、作成することでDB接続周りを一手に引き受けられる
org.dbunit.AbstractDatabaseTester
を継承する
コードカバレッジ
カバレッジの種類
- C0(命令網羅)
- C1(分岐網羅)
- すべての分岐(if)を1回以上実行したかを測定
if
条件のtrue
orfalse
のどちらも通す必要がある
- C2(条件網羅)
- すべての条件を1回以上実行したかを測定
if
条件のtrue
になる全ての条件とfalse
になるすべての条件を見る必要がある
カバレッジ測定ツール
- [EclEMMA(http://www.eclemma.org/)
- Jacoco
- カバレッジ測定エンジン
- Mavenにライブラリとして追加 Maven Repository
# テスト実行 + カバレッジ測定 % mvn clean jacoco:prepare-agent test jacoco:report # カバレッジレポート参照 % target/site/jacoco/index.html
おわりに
JUnitを利用した、ユニットテストに関してざっくりとした記事を書きました。
昨今は、マイクロサービス化の利用等でますますユニットテストの価値が上がってきているかと思います。
(テストがないコードはレガシーとまで言われるかもしれないですね。。)
テストを何のために書くのかや、どういったテストが良いテストなのかについては特に記載しませんでした。
そのあたりは自分で考えていただいて、テストの有用性に対して正確な理解を個人でしてもらえればと思います。
また、自分で利用したことがないため、テストランナーについてあまり記載できていませんでしたので、
学んだ際に追記できればと思います。