既存システムに対してテストコードを書く(1/2)

JUnitを始めとするテストツールが充実した現在も、運用・保守の現場では、テストコードが存在しないJavaの既存システムを目にすることがあります。このようなシステムに対して、後付でテストコードを書こうとすると、大抵は以下の問題に突き当たって、モックの作成が難しくなります。

  • テスト対象のコードが、staticメソッドを呼び出している
  • テスト対象のコードが、技術的なAPIをnewして使っている

例えば、次のようなコードです。

public class InterestCalclator {
	public int calcInterest(int principal) {

		// 日数を取得
		int days = DBAdapter.getCalcDaysFromDB();

		// 実質年率の値をファイルから取得
		BigDecimal interestRate = null;
		try {
			BufferedReader br = new BufferedReader(new FileReader("interestRate.txt"));
			String line = br.readLine();
			interestRate = new BigDecimal(line);
			br.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}

		// 利子を計算
		BigDecimal principalDec = BigDecimal.valueOf(principal);
		BigDecimal daysDec = BigDecimal.valueOf(days);
		BigDecimal result = principalDec.multiply(interestRate).multiply(daysDec.divide(BigDecimal.valueOf(365), 5, BigDecimal.ROUND_HALF_EVEN));
		return result.round(new MathContext(0, RoundingMode.HALF_EVEN)).intValue();
	}
}

public class DBAdapter {
	public static int getCalcDaysFromDB() {
		int days = 0;
		// JDBCを使って、データベースから値を取得
		// 処理の記述は省略します
		return days;
	}
}

InterestCalclatorクラスのcalcInterestメソッドがテスト対象のコードです。

メソッドの初めに、DBAdapterクラスのstaticメソッドのgetCalcDaysFromDBメソッドを呼び出して、日数を取得しています。getCalcDaysFromDBメソッドは、JDBCを使ってデータベースにアクセスします。テストするにはデータベースを準備する手間が発生しますので、getCalcDaysFromDBメソッドをモックに変更したいところです。

次に、BufferedReaderクラスやFileReaderクラスをnewしてオブジェクトを作成し、実質年率の値をファイルから読み取っています。テストするにはファイルの準備が必要になるため、これらのオブジェクトもモックに変更したいところです。

JMockItを使った解決

著名なモックフレームワークのEasyMockやMockitoでは、この問題は解決できませんが、後発のJMockItというモックフレームワークであれば解決することが出来ます。

JMockItを使うと、以下のようなテストコードが書けます。

public class InterestCalclatorTest {
	@Test
	public void testCalcInterest() throws Exception {
		new NonStrictExpectations(DBAdapter.class) {
			{
				DBAdapter.getCalcDaysFromDB();
				result = 30;
			}
		};
		new NonStrictExpectations() {
			BufferedReader br;
			FileReader fr;
			{
				br.readLine();
				result = "0.032";
			}
		};
		InterestCalclator target = new InterestCalclator();
		int interest = target.calcInterest(1000000);
		assertEquals(2630, interest);
	}
}

NonStrictExpectationsというクラスは、JMockItが提供するクラスです。詳しい説明は省略しますが、DBAdapter.getCalcDaysFromDBメソッドと、BufferedReaderとFileReaderがモックにすり替えられています。
JMockItの解説は、ryoasaiさんやj5ik2oさんのブログに詳しく書かれていますので、興味のある方は参照することをお勧めします。

では、JMockItをどんどん使って既存システムのテストコードを書けばいいかと言うと、そうでもありません。「力の悪用の例(英語の原文はAn example of power corrupting)」というサブタイトルのModern Mocking Tools and Black Magicという記事に興味深い問題の指摘が書いてあります。

その内容については、次回のブログで触れたいと思います。