システム日付を安易に使ったプログラムのテスト
全くテストを意識しないで Calendar.getInstance() とか new Date() とかを使いまくるプログラムがあったとする。例えば、以下のようなものだ。
public void oomisoka() { Date date = new Date(); if (date.getMonth() == 11 && date.getDate() == 31) { // 12月31日にのみ行う処理… } }
せめて引数で 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:使い込んでいないためもっといい方法がある可能性は大