1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

リフレクションユーティリティを作る①

Last updated at Posted at 2019-03-16

はじめに

時々単体をJUnitでやるけど、いちいちプライベートメソッドを実行する処理を作るのが面倒なのでここにメモ。テストに限った話ではないですがおそらくテストコードにしか使わなそう。

privateメソッドを実行するメソッド

Params.java
package com.example.demo.util;

import java.lang.reflect.Method;

public class ReflectionUtil {

	/**
	 * プライベートメソッドを実行する(引数なし).
	 * @param obj メソッドを実行するインスタンス
	 * @param methodName メソッド名
	 * @param params 引数
	 * @return メソッドの戻り値
	 * @throws Exception
	 */
	public static Object invokePrivate(Object obj, String methodName) throws Exception {
		Class<?> clazz = obj.getClass();
		Method method = clazz.getDeclaredMethod(methodName);
		method.setAccessible(true);
		return method.invoke(obj);
	}

	/**
	 * プライベートメソッドを実行する(引数あり).
	 * @param obj メソッドを実行するインスタンス
	 * @param methodName メソッド名
	 * @param params 引数
	 * @return メソッドの戻り値
	 * @throws Exception
	 */
	public static Object invokePrivate(Object obj, String methodName, Params params) throws Exception {
		if(params == null ){
			throw new NullPointerException("params is required");
		}
		Class<?> clazz = obj.getClass();
		Method method = clazz.getDeclaredMethod(methodName, params.getTypes());
		method.setAccessible(true);
		return method.invoke(obj, params.getArgs());
	}
}

ちなみに、getDeclaredMethodはjava.lang.Classオブジェクトからメソッドオブジェクトを取得するので、staticメソッドの場合はインスタンス化する必要はないです。なのでinvokePrivateの第一引数にクラスを直接渡してgetDeclaredMethodを実行すればOK。

引数をまとめるクラス

上記の引数ありprivateメソッドを実行するために必要な引数をまとめるためのクラス。
※一部lombok使用。

Params.java
package com.example.demo.util;

import java.util.ArrayList;
import java.util.List;

import lombok.Data;

public class Params {

	private List<Param> paramList;

	public Params() {
		paramList = new ArrayList<>();
	}

	/**
	 * 初期化と同時に引数指定するコンストラクタ.
	 * @param type
	 * @param arg
	 */
	public <T> Params(Class<T> type, T arg) {
		this();
		add(type, arg);
	}

	/**
	 * 引数とその型を設定.
	 * @param type
	 * @param arg
	 */
	public <T> void add(Class<T> type, T arg) {
		paramList.add(new Param(type, arg));
	}

	/**
	 * 設定した引数の型を配列で返す.
	 * @return
	 */
	public Class<?>[] getTypes() {
		Class<?>[] types = new Class[paramList.size()];
		for (int i = 0; i < paramList.size(); i++) {
			types[i] = paramList.get(i).getType();
		}
		return types;
	}

	/**
	 * 設定した引数を配列で返す.
	 * @return
	 */
	public Object[] getArgs() {
		Object[] args = new Object[paramList.size()];
		for (int i = 0; i < paramList.size(); i++) {
			args[i] = paramList.get(i).getArg();
		}
		return args;
	}

	/**
	 * 引数1つを表すクラス.<br />
	 * 引数オブジェクトとその型を属性に持つ。
	 *
	 */
	@Data
	private class Param {
		private Class<?> type;
		private Object arg;

		<T> Param(Class<T> type, T arg) {
			this.type = type;
			this.arg = arg;
		}
	}
}

実行してみる

  • テスト対象privateメソッド
  • テストケース
  • 実行結果

テスト対象privateメソッド

引数を持つprivateメソッドを適当に用意。

Test.java
	private String doSomthing(int i, Long l, String str){
		System.out.println(Integer.toString(i));
		System.out.println(l.toString());
		System.out.println(str);
		return "完了!";
	}

テストケース

UtilTest.java
	@Test
	public void test1() {
		AService test = new AService();	
		// コンストラクタで1個目の引数を指定してみる
		Params params = new Params(int.class, 1);
		// あとはaddで追加
		params.add(Long.class, 20L);
		params.add(String.class, "3つ目の引数");
		try {
			// 実行
			String ret = (String) ReflectionUtil.invokePrivate(test, "doSomthing", params);
			System.out.println(ret);
		} catch (Exception e) {
			e.printStackTrace();
			fail();
		}
	}

実際に使うときはinvokePrivateはstaticインポートした方が見やすいですね。

実行結果

実行結果
1
20
3つ目の引数
完了!

ちなみに、

UtilTest.java
	/**
	 * paramsの代わりにnullを指定した場合.
	 */
	@Test
	public void testParamsNull() {
		AServices = new AService();
		try {
			ReflectionUtil.invokePrivate(s, TARGET_METHOD, null);
			fail();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	 * paramsに設定した引数が一致しなかった場合.
	 */
	@Test
	public void testParamsInvalid() {
		AService s = new AService();
		Params params = new Params(int.class, 1);
		params.add(Long.class, 20L);
		try {
			ReflectionUtil.invokePrivate(s, TARGET_METHOD, params);
			fail();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

実行結果 testParamsNull()
java.lang.NullPointerException: params is required
	at com.example.demo.util.ReflectionUtil.invokePrivate(ReflectionUtil.java:32)
	at com.example.demo.UtilTest.test1(UtilTest.java:33)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	:
実行結果 testParamsInvalid()
java.lang.NoSuchMethodException: com.example.demo.service.AService.doSomthing(int, java.lang.Long)
	at java.lang.Class.getDeclaredMethod(Class.java:2122)
	at com.example.demo.util.ReflectionUtil.invokePrivate(ReflectionUtil.java:35)
	at com.example.demo.UtilTest.test1(UtilTest.java:33)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	:

以上。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?