JUnitでprivate scopeなものをテストする方法


private scopeなものをテストする

Javaでprivate scopeのフィールド、メソッド、内部クラスを外部から呼び出し、
JUnitでテストしてみます。
※何番煎じか、出涸らしかという記事です。

まず、JUnitでテストしようとしてみます。

コンパイルエラーです。仕様です。実行できません。
privateなので、publicと同じようには呼び出せません。

そこでReflectionを使ってprivate scopeなものを呼び出します。

Reflectionとは

Reflectionとは、Javaの標準ライブラリの一種で
クラスそのものの情報を扱うことです。
具体的な例として、
クラス名、メソッド名、フィールド名の文字列で
そのクラスを生成、メソッド実行、フィールドへのアクセスが
できたりします。

以下、外部サイトですが、例えばこんな感じです。
http://java.keicode.com/lang/reflection.php

「こんなに便利なら積極的に使おう」
と思う方もいるかもしれません。
ですが、Reflectionは諸刃の剣です。取扱厳重注意です。
しかし、敢えて紹介します。

Reflectionの問題点

Reflectionは使い方次第で

  • クラス設計が崩壊する
  • コードが書きにくいし読みにくくなる
  • パフォーマンスが普通にメソッドを呼び出すより悪い

など、問題点もあります。
しかし、敢えて紹介します。

「JUnitでprivate scopeなものをテストする」
という限定した目的で使うためです。

本題に戻って

さっきのエラーだらけのJUnitのテストクラスを直してみます。

package jp.co.illmatics.sample;
/**
 * テスト対象のクラス
 */
@SuppressWarnings("unused")
public class PrivateSample {

    private String field = "Hello field!";

    private final String finalField = "Hello final field!";

    private String doIt()    {
        return "Hello method!";
    }

    private class InnerClass {
        public InnerClass() {

        }
        public String getInnerMethod() {
            return "Hello inner method!";
        }
    }
}
package jp.co.illmatics.sample;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.junit.Test;

import junit.framework.TestCase;

/**
 * テストクラス
 */
public class PrivateSampleTest extends TestCase {

    PrivateSample privateSample = new PrivateSample();
    String expected = "";
    String actual = "";

    @Test
    public void testField() throws Exception {
        final String fieldName = "field";
        expected = "Hello field!";
        actual = (String) getFieldValue(privateSample, fieldName);
        assertEquals(expected, actual);
    }

    @Test
    public void testFinalField() throws Exception {
        final String finalFieldName = "finalField";
        expected = "Hello updated final Field!";
        actual = (String) getUpdatedFinalFieldValue(privateSample, expected, finalFieldName);
        assertEquals(expected, actual);
    }

    @Test
    public void testMethod() throws Exception {
        final String methodName = "doIt";
        expected = "Hello method!";
        actual = (String) getMethodValue(privateSample, methodName);
        assertEquals(expected, actual);
    }

    @Test
    public void testInnerClassMethod() throws Exception {
        final String innerClassName = "jp.co.illmatics.sample.PrivateSample$InnerClass";
        final String innerMethodName = "getInnerMethod";
        expected = "Hello inner method!";
        actual = (String) getInnerClassMethod(privateSample, innerClassName, innerMethodName);
        assertEquals(expected, actual);
    }

    private Object getFieldValue(PrivateSample privateSample, String fieldName)
            throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
            SecurityException {
        return getPrivateField(privateSample, fieldName).get(privateSample);
    }

    private Field getPrivateField(PrivateSample privateSample, String fieldName)
            throws NoSuchFieldException, SecurityException, IllegalArgumentException,
            IllegalAccessException {
        Class<?> clazz = privateSample.getClass();
        Field field = clazz.getDeclaredField(fieldName);
        field.setAccessible(true);
        return field;
    }

    private Object getUpdatedFinalFieldValue(PrivateSample privateSample, String newValue,
            String fieldName) throws NoSuchFieldException, SecurityException,
                    IllegalArgumentException, IllegalAccessException {
        Field finalField = getPrivateField(privateSample, fieldName);
        finalField.set(privateSample, newValue);
        return finalField.get(privateSample);
    }

    private Object getMethodValue(PrivateSample privateSample, String name)
            throws NoSuchMethodException, SecurityException, IllegalAccessException,
            IllegalArgumentException, InvocationTargetException {
        Method doIt = PrivateSample.class.getDeclaredMethod(name);
        doIt.setAccessible(true);
        return doIt.invoke(privateSample);
    }

    private Object getInnerClassMethod(PrivateSample parent, String classFullName,
            String methodName) throws ClassNotFoundException, NoSuchMethodException,
                    SecurityException, InstantiationException, IllegalAccessException,
                    IllegalArgumentException, InvocationTargetException {
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        Class<?> innerClazz = loader.loadClass(classFullName);
        Constructor<?> constructor = innerClazz.getDeclaredConstructor(parent.getClass());
        constructor.setAccessible(true);
        Object innerObj = constructor.newInstance(parent);
        return innerClazz.getDeclaredMethod(methodName).invoke(innerObj);
    }
}


直せました。


テストします。


できました。