Java,JUnit,assertThrows

書いた。

説明

JUnit用のアサーションメソッド。例外表明を1行で書ける。

try {
	buckets.get(1000);
	fail();
} catch (IndexOutOfBoundsException e) {
}

これが

assertThrows(IndexOutOfBoundsException.class, buckets, "get", 1000);

こうなる。

スタティックメソッドにも対応。

// BinaryUtils.decodeInt(ba(0,1,2))
assertThrows(
	IllegalArgumentException.class,
	BinaryUtils.class,
	"decodeInt",
	ba(0, 1, 2));

ソース

パラメータの型からメソッドを解決する処理は自前で書いた。
assertThrowsに渡すときボクシングが発生するのでメソッドの型を推測するときちょっと面倒、あとこの実装だとf(int)とf(Integer)の区別が怪しいっていうかf(Object)とf(String)すら怪しいわけですがぼくのユースケースではうまく動いてるので何の問題もないですね。
メソッド解決を本気でやろうとすると結構面倒なわけだがjavaのライブラリでそういう処理提供されてないんですかね。

public class TestHelper {
	public static void assertThrows(Class<? extends Throwable> expected,
			Class<?> targetClass, String methodName, Object... args)
			throws Exception {
		assertThrows(expected, null, findMethod(
			targetClass,
			methodName,
			getTypeArray(args)), args);

	}

	public static void assertThrows(Class<? extends Throwable> expected,
			Object obj, String methodName, Object... args) throws Exception {
		assertThrows(expected, obj, findMethod(
			obj.getClass(),
			methodName,
			getTypeArray(args)), args);

	}

	private static void assertThrows(Class<? extends Throwable> expected,
			Object target, Method method, Object[] args) throws Exception {
		try {
			method.invoke(target, args);
		} catch (Exception e) {
			if (e instanceof InvocationTargetException) {
				final Throwable actual =
					((InvocationTargetException) e).getTargetException();
				if (actual.getClass() == expected)
					return; // success
			}
			throw e;
		}
	}

	private static Class<?>[] getTypeArray(Object... values) {
		final Class<?>[] paramTypes = new Class<?>[values.length];
		for (int i = 0; i < paramTypes.length; i++)
			paramTypes[i] = values[i].getClass();
		return paramTypes;
	}

	private static Method findMethod(Class<?> klass, String methodName,
			Class<?>[] argTypes) throws NoSuchMethodException {
		try {
			return klass.getMethod(methodName, argTypes);
		} catch (NoSuchMethodException noSuchMethod) {
			final Method[] methods = klass.getMethods();
			find_method: for (Method m : methods) {
				if (!m.getName().equals(methodName))
					continue find_method;
				final Class<?>[] paramTypes = m.getParameterTypes();
				if (paramTypes.length != argTypes.length)
					continue find_method;
				for (int i = 0; i < paramTypes.length; i++) {
					if (!isSameType(paramTypes[i], argTypes[i]))
						continue find_method;
				}
				return m;
			}
			throw noSuchMethod;
		}
	}

	private static boolean isSameType(Class<?> type,
			Class<?> type_maybe_boxed_or_sub) {
		// exactry same
		if (type == type_maybe_boxed_or_sub)
			return true;

		// subtype
		if (type.isAssignableFrom(type_maybe_boxed_or_sub))
			return true;

		// array
		if (type.isArray()) {
			if (type_maybe_boxed_or_sub.isArray())
				return isSameType(
					type.getComponentType(),
					type_maybe_boxed_or_sub.getComponentType());
			else
				return false;
		}

		// primitive
		if (!type.isPrimitive())
			return false;
		if (type == Byte.TYPE && type_maybe_boxed_or_sub == Byte.class)
			return true;
		if (type == Short.TYPE && type_maybe_boxed_or_sub == Short.class)
			return true;
		if (type == Integer.TYPE && type_maybe_boxed_or_sub == Integer.class)
			return true;
		if (type == Long.TYPE && type_maybe_boxed_or_sub == Long.class)
			return true;

		if (type == Boolean.TYPE && type_maybe_boxed_or_sub == Boolean.class)
			return true;

		if (type == Character.TYPE
			&& type_maybe_boxed_or_sub == Character.class)
			return true;

		if (type == Float.TYPE && type_maybe_boxed_or_sub == Float.class)
			return true;
		if (type == Double.TYPE && type_maybe_boxed_or_sub == Double.class)
			return true;

		return false;
	}
}