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

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

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

```java: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使用。

```java: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;
	}

	/**
	 * 引数１つを表すクラス.<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メソッドを適当に用意。

```java: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 "完了！";
	}
```

## テストケース

```java: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, "３つ目の引数");
		try {
			// 実行
			String ret = (String) ReflectionUtil.invokePrivate(test, "doSomthing", params);
			System.out.println(ret);
		} catch (Exception e) {
			e.printStackTrace();
			fail();
		}
	}
```
実際に使うときはinvokePrivateはstaticインポートした方が見やすいですね。


## 実行結果

```:実行結果
1
20
３つ目の引数
完了！
```


ちなみに、

```java: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)
	：
```

以上。