システム日付を安易に使ったプログラムのテスト

全くテストを意識しないで Calendar.getInstance() とか new Date() とかを使いまくるプログラムがあったとする。例えば、以下のようなものだ。

public void oomisoka() {
    Date date = new Date();
    if (date.getMonth() == 11 && date.getDate() == 31) {
        // 12月31日にのみ行う処理…
    }
}

せめて引数で Date を受け取るようになっていれば…という感じだが、こういったメソッドをテストするにはどうしたらいいだろう?

  • ユニットテスト実行前にシステム時刻を手動で変更
  • ユニットテスト内で date コマンドを実行してシステム日付を変更し、テスト終了後元に戻す
  • あきらめる

など様々な対処を見たが、JMockit などを使えば上記のような挑戦的プログラムも普通にテストができる。

たとえば、new Date() や Calendar.getInstance() で作られるインスタンスの年を2000年に変更してしまうには、以下のようにすれば良い*1

package com.example.mockdate;

import static junit.framework.Assert.assertEquals;

import java.util.Calendar;
import java.util.Date;

import mockit.Mock;
import mockit.MockUp;
import mockit.Mockit;

import org.junit.BeforeClass;
import org.junit.Test;

public class AppTest {

	@BeforeClass
	public static void setUpClass() {
		// 大抵不要だが一応
		// see: http://jmockit.googlecode.com/svn/trunk/www/tutorial/RunningTests.html
		Mockit.setUpMocks();
	}
	
	// staticメソッドのモックを作るにはクラス定義が必要?
	public static class MockCalendar {
		private static Calendar instance;
		
		public static void setInstance(Calendar desired) {
			instance = (Calendar)desired.clone();
		}
		
		@Mock
		public static Calendar getInstance() {
			return (Calendar)instance.clone();
		}
	}

	@Test
	public void testCalendar() {
		// MockCalendar.getInstance() が返す Calendar を設定
		Calendar desired = Calendar.getInstance();
		desired.set(Calendar.YEAR, 2000);
		MockCalendar.setInstance(desired);
		
		// Calendarクラスのモックとして MockCalendar を指定
		Mockit.setUpMock(Calendar.class, MockCalendar.class);
		
		// millennium now!
		assertEquals(2000, Calendar.getInstance().get(Calendar.YEAR));
	}

	@Test
	@SuppressWarnings({ "unused", "deprecation" })
	public void testMockDate() {
 		new MockUp<Date>() {
			Date it;
			
			// Date のデフォルトコンストラクタの差し替え
			// インスタンス変数 it で、コンストラクタが返す値を操作できる
			@Mock
			void $init() {
				it.setYear(100);
			}
		};

		// millennium again!
		assertEquals(100, new Date().getYear());
	}
}

*1:使い込んでいないためもっといい方法がある可能性は大