4-1. テストの作り方

ユニットテスト

プログラムは完璧に作られていないと動作しません。 しかし、完成直前まで全く動かないような作り方をすべきではありません。 オブジェクト指向により、様々なソフトウェアの機能をクラスに分割でき るようになりました。 そのため、クラス単体で動作テストができる必要があります。 プログラムの部分をテストすることをユニットテストと言います。

xUnit と呼ばれる、様々なプログラミング言語用のユニットテスト用のツー ルが開発されており、Java 用に JUnit が開発されています。 これを用いると、自動ユニットテストができ、クラスごとに完璧性がテス トできます。

さらに Eclipse にはJUnit 用のツールが用意されていて、テストを作り やすくなっています。

テストファースト

人間が大きな仕事をする場合、一つ一つの積み重ねを端からコツコツやっ ていくよりは、仕事を小さく分割して、マイルストーンを設け、段階的に 作成したほうが、最終的な手間が若干増えるとしても失敗を少なくできま す。

4-2. JUnit

テストの実際

特定のクラス A のメソッド bと c をテストすることを考えます。 サンプルとして次を考えます。

サンプルクラス


public class A {
  public int b(int x){ return x+1; }
  public boolean c(int x){ return x==1;}
}

このクラスを Eclipse で選択して、「New」→「JUnit Test Case」を選択す ると、テストクラスを作成するウィザードが表示されます。 「New JUnit Jupiter Test」を指定し、「setUP」を選択して Next を押します。 そして、次の画面でテストしたいメソッドを選択し、「Finish」を押します。 すると、Eclipse に含まれている JUnit のライブラリを使うかのメッセージ 「Add JUnit 5 library to the build path」のメッセージが出ますので、承 認します。

すると、次のコードを生成します。

Eclipse の生成したテストコード


package spro4;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class ATest {
	@BeforeEach
	void setUp() throws Exception {
	}
	@Test
	void testB() {
		fail("Not yet implemented");
	}
	@Test
	void testC() {
		fail("Not yet implemented");
	}
}

なお、 Java9 以降で module-info.java を作成した場合、作ったテストクラスで junit 関連でエラーが生じます。 この場合、 module-info.java に module の記述が必要になります。下記のよ うに、junit を記述します。 しかし、指定した junit のパッケージに赤線が出ます。 そこで、その赤線にカーソルを載せると。「Move classpath entry 'JUnit 5' to modulepath」という quick fix が表示されるので、それをクリックすることですべてのエラーが解消します。


module spro4 {
	requires org.junit.jupiter.api;
}

まず、テストクラスの名前は「テストするクラス名+Test」になります。 但し、これは慣習なので、実際にはテストメソッド名は何でも良いです。 前処理のメソッドは @BeforeEach 、テストメソッドは @Test のアノテーションを 付けると自動的にテストが順番に行われます。 この段階でこのクラスを Run as JUnit として実行すると、JUnit 用の専用のテスト 集計画面が出て、上に結果、下にメッセージが表示されます。 この場合、テストが失敗したことを表す赤いバーが出ます。 そして、下には「java.lang.Assertion Error: Not yet implemented」という メッセージが出ています。 これは上のプログラムの fail() という必ずテストが失敗するメソッドの中の メッセージが表示されています。

テスト作成の基本は setUp でメンバ変数にインスタンスを生成し、各テスト メソッドで値を入れて、チェックします。 各テストメソッドでは org.junit.jupiter.api.Assertions の 各 メソッド を使用してテストの成否を決めます。 値のチェックは assertEquals(目標値, 式) とします。 なお、 assertEquals(true, 式) は assertTrue(式)、 assertEquals(false, 式) は assertFalse(式) を使えます。 また、equals ではなく、 == で比較したい場合は assertSame と assertNotSame を使用します。

上記のサンプルクラスのテストとして次のようなテストに作成します。

サンプルクラスのテストコード


package test;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class ATest {
	private A a;
	@BeforeEach
	void setUp() throws Exception {
		a=new A();
	}
	@Test
	void testB() {
		assertEquals(2,a.b(1));	
		assertEquals(11,a.b(10));	
	}
	@Test
	void testC() {
		assertFalse(a.c(0));
		assertTrue(a.c(1));
		assertFalse(a.c(2));
	}
}

テストは基本的に値を入れて取り出して確認するだけです。 必要最低限のテストを用意すべきですが、少なすぎると、テストを通っても使 用できないプログラムが出来上がる可能性があります。

なお、このテストプログラムを先に作成して、テストが通るようにプログラム を作成することを「テストファースト」と言います。

演習4-1

テスト環境を作ります。

  1. src フォルダを右クリックして new → package で test パッケージ を作る。
  2. A.java を右クリックして new→Other→Java→JUnit→JUnit Test Case を選び next
  3. Package を test にし、 setUp をチェックして next
  4. テストを作るメソッドとして compareTo をチェックして Finish する
  5. 以下のプログラムを入れる
    
    package test;
    import static org.junit.jupiter.api.Assertions.*;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import spro2.A;
    import spro2.A998;
    import spro2.A999;
    class ATest {
    	private A999 a999;
    	private A998 a998;
    
    	@BeforeEach
    	void setUp() throws Exception {
    		a999 = new A999();
    		a998 = new A998();
    	}
    
    	@Test
    	void testCompareTo() {
    		assertTrue(a998.compareTo(a999)<0);
    		assertTrue(a998.compareTo(a998)==0);
    		assertTrue(a999.compareTo(a999)==0);
    		assertTrue(a999.compareTo(a998)>0);
    	}
    	@Test
    	void testEquals() {
    		assertFalse(a998.equals(null));
    		assertFalse(a998.equals("99ec999"));
    		assertTrue(a998.equals(a998));
    		assertFalse(a998.equals(a999));
    	}
    	private boolean hashCodeCondition(A a1, A a2) {
    		return !a1.equals(a2)||(a1.hashCode()==a2.hashCode());
    	}
    	@Test
    	void testHashCode() {
    		assertTrue(hashCodeCondition(a998,a998));
    		assertTrue(hashCodeCondition(a998,a999));
    		assertTrue(hashCodeCondition(a999,a998));
    		assertTrue(hashCodeCondition(a999,a999));
    	}
    }
    
  6. ここで一旦コミットする