山崎屋の技術メモ

IT業界で働く中で、気になること、メモしておきたいことを書いていきます。

【JUnit】ユニットテストの基本を整理

JUnit を使ったユニットテストについての基本を、命名規則やお作法なども交え、自分なりに整理してみました。

基本のみなので、細かいバージョンは意識しませんが、JUnit 4.12 を使用します。

環境準備

Eclipse を使用して Maven プロジェクトを作成します。Maven プロジェクトの作り方は、このあたりを参考にしてください。
Eclipse で Maven プロジェクトの作成からJDKバージョンの設定 - 山崎屋の技術メモ

加えて、pom ファイルに 依存関係を追記します。最終形の pom です。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.yyama</groupId>
	<artifactId>park</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
	</properties>
	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

テスト対象となるクラスを定義します。ここではサンプルで遊園地( Park )クラスを定義しました。

package org.yyama;

public class Park {

	// 基本入場料
	private int baseFee;

	// 引数なしコンストラクタが呼ばれた場合、基本入場料は 0 円で初期化。
	public Park() {
		baseFee = 0;
	}

	// 引数ありコンストラクタ
	public Park(int baseFee) {
		setBaseFee(baseFee);
	}

	// 基本入場料をセットしなおします。
	public void setBaseFee(int baseFee) {
		if (baseFee < 0) {
			throw new IllegalArgumentException("baseFee は 0以上としてください。[" + baseFee + "]");
		}
		this.baseFee = baseFee;
	}

	// 年齢によって異なる入場料を返します。
	public int getFee(int age) {
		if (age < 0) {
			throw new IllegalArgumentException("age は 0以上としてください。[" + age + "]");
		}
		
		// 5 才以下は無料
		if (age <= 5) {
			return 0;
		}
		
		// 12 才以下は半額
		if (age <= 12) {
			return (int) ((double) baseFee * 0.5);
		}
		
		// 60 才以上は 25 % 引き
		if (age >= 60) {
			return (int) ((double) baseFee * 0.75);
		}
		return baseFee;
	}
}

これで準備は整いました。さあテストクラスを作っていきましょう。

テストクラス名

まず、クラス名ですが、「Testテスト対象クラス名_テスト対象メソッド名」とします。具体的には「TestPark_getFee」。

シンプルなクラスをテストするのであれば「Testテスト対象クラス名」でもいいと思います(例えば「TestPark」)が、私の経験した業務システムでは、ひとつのメソッドに対し、10 個も 20 個もテストケースを作成する必要がある場合が多く(クラス設計がいまいち)、メソッドごとにテストクラスを分けたほうがシンプルなユニットテストが書ける時が多いです。

メソッド名

メソッド名は日本語で、テスト条件と結果の期待値を書くことが多いです。

例えば「引数が1と2の場合、3を返す」みたいな感じです。

コンストラクタのテスト

では、実際にユニットテストを作成していきたいと思います。

まず、コンストラクタですが、引数ありなしの2種類がありますが、テストクラスは 1 個にまとめてしまいます。ここら辺はテスト対象クラスに応じて柔軟に対応しましょう。

テストクラス名は「TestPark_Park」とします。

引数なしコンストラクタのテスト。

	public void 引数なしコンストラクタは基本入場料が0になる() {
		// 実行
		Park park = new Park();
		// 検証
		assertEquals(0, park.getFee(20));
	}

基本的にテストメソッドでは、「準備」・「実行」・「検証」の順番で行いますが、ここでは準備不要なので「実行」と「検証」のみ行います。

検証では、基本入場料( baseFee )を直接参照できないので getFee メソッドに 20 を渡すことで確認しています。

次に引数ありコンストラクタのテスト。

	@Test
	public void コンストラクタに1000を渡すと基本入場料は1000になる() {
		// 実行
		Park park = new Park(1000);
		// 検証
		assertEquals(1000, park.getFee(20));
	}

setBaseFee メソッドのテスト

次に setBaseFee メソッドのテストです。テストクラス名は「Park_setBaseFee」とします。

まず、引数に 1000 を渡すと基本入場料が 1000 になることを確認します。
今回はオブジェクト作成の準備が必要になります。

	@Test
	public void 引数に1000を渡すとは基本入園料が0になる() {
		// 準備
		Park park = new Park();
		
		// 実行
		park.setBaseFee(1000);
		
		// 検証
		assertEquals(1000, park.getFee(20));
	}

引数にマイナスを渡した場合、Exception が発生することを確認します。

	@Test(expected = IllegalArgumentException.class)
	public void 引数にマイナス1を渡すとIllegalArgumentExceptionが発生する() {
		// 実行
		new Park(-1);
	}

見てのとおり Test アノテーションの引数「expected」に例外のクラスを指定しています。

getFee メソッドのテスト

getFee メソッドは引数の値により、複数の分岐があります。判定の境界となる値をテストします。具体的には -1, 0, 5, 6, 12, 13, 59, 60 の値でテストします。ここでは -1 と 0 の例を示します。

	@Test(expected = IllegalArgumentException.class)
	public void 引数にマイナス1を渡すとIllegalArgumentExceptionが発生する() {
		// 準備
		Park park = new Park(1000);

		// 実行
		park.getFee(-1);
	}

	@Test()
	public void 引数に0を渡すと0が返る() {
		// 準備
		Park park = new Park(1000);

		// 実行
		int r = park.getFee(0);

		// 検証
		assertEquals(0, r);
	}

引数 0 のケースで、戻り値をいったん変数 r に値を入れて実行と検証を明確に分けていますが、「assertEquals(0, park.getFee(0))」のように実行と検証を一緒にしてもいいでしょう。

まとめ

この記事のやり方をベースに、プロジェクトの特性を考慮しながらルールをカスタマイズしていくのがいいと思います。

プロダクトコードと一緒ですが、テストクラスの数が多くなっても分かりやすいように整理整頓することが大事です。

最後にテストクラスの全量を記載しておきます。

TestPark

package org.yyama;

import static org.junit.Assert.*;

import org.junit.Test;

public class TestPark_Park {
	@Test
	public void 引数なしコンストラクタは基本入園料が0になる() {
		// 実行
		Park park = new Park();
		// 検証
		assertEquals(0, park.getFee(20));
	}

	@Test
	public void コンストラクタに1000を渡すと基本入園料は1000になる() {
		// 実行
		Park park = new Park(1000);
		// 検証
		assertEquals(1000, park.getFee(20));
	}
}

TestPark_setBaseFee

package org.yyama;

import static org.junit.Assert.*;

import org.junit.Test;

public class TestPark_setBaseFee {
	@Test
	public void 引数に1000を渡すとは基本入園料が0になる() {
		// 準備
		Park park = new Park();
		
		// 実行
		park.setBaseFee(1000);
		
		// 検証
		assertEquals(1000, park.getFee(20));
	}

	@Test(expected = IllegalArgumentException.class)
	public void 引数にマイナス1を渡すとIllegalArgumentExceptionが発生する() {
		// 実行
		new Park(-1);
	}	
}

TestPark_getFee

package org.yyama;

import static org.junit.Assert.*;
import org.junit.Test;

public class TestPark_getFee {
	@Test(expected = IllegalArgumentException.class)
	public void 引数にマイナス1を渡すとIllegalArgumentExceptionが発生する() {
		// 準備
		Park park = new Park(1000);

		// 実行
		park.getFee(-1);
	}

	@Test()
	public void 引数に0を渡すと0が返る() {
		// 準備
		Park park = new Park(1000);

		// 実行
		int r = park.getFee(0);

		// 検証
		assertEquals(0, r);
	}

	@Test()
	public void 引数に5を渡すと0が返る() {
		// 準備
		Park park = new Park(1000);

		// 実行
		int r = park.getFee(5);

		// 検証
		assertEquals(0, r);
	}

	@Test()
	public void 基本入場料1000のとき_引数に6を渡すと500が返る() {
		// 準備
		Park park = new Park(1000);

		// 実行
		int r = park.getFee(6);

		// 検証
		assertEquals(500, r);
	}

	@Test()
	public void 基本入場料1000のとき_引数に12を渡すと500が返る() {
		// 準備
		Park park = new Park(1000);

		// 実行
		int r = park.getFee(12);

		// 検証
		assertEquals(500, r);
	}

	@Test()
	public void 基本入場料1000のとき_引数に13を渡すと1000が返る() {
		// 準備
		Park park = new Park(1000);

		// 実行
		int r = park.getFee(13);

		// 検証
		assertEquals(1000, r);
	}

	@Test()
	public void 基本入場料1000のとき_引数に59を渡すと1000が返る() {
		// 準備
		Park park = new Park(1000);

		// 実行
		int r = park.getFee(59);

		// 検証
		assertEquals(1000, r);
	}

	@Test()
	public void 基本入場料1000のとき_引数に60を渡すと750が返る() {
		// 準備
		Park park = new Park(1000);

		// 実行
		int r = park.getFee(60);

		// 検証
		assertEquals(750, r);
	}
}

ご意見などがあればお気軽にコメントしてください。

それでは!

知識ゼロから学ぶソフトウェアテスト 【改訂版】

知識ゼロから学ぶソフトウェアテスト 【改訂版】

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

実践 JUnit ―達人プログラマーのユニットテスト技法

実践 JUnit ―達人プログラマーのユニットテスト技法