7

クラスの getClass() メソッドをモックすることは避けたいのですが、それを回避する方法が見つからないようです。オブジェクト クラス タイプを HashMap に格納するクラスを、後で使用する特定のメソッドにテストしようとしています。これの簡単な例は次のとおりです。

public class ClassToTest {
    /** Map that will be populated with objects during constructor */
    private Map<Class<?>, Method> map = new HashMap<Class<?>, Method>();

    ClassToTest() {
        /* Loop through methods in ClassToTest and if they return a boolean and 
           take in an InterfaceA parameter then add them to map */
    }

    public void testMethod(InterfaceA obj) {
        final Method method = map.get(obj.getClass());
        boolean ok;
        if (method != null) {
             ok = (Boolean) method.invoke(this, obj);
        }
        if (ok) {
            obj.run();
        }
    }

    public boolean isSafeClassA(final ClassA obj) {
        // Work out if safe to run and then return true/false
    }

    public boolean isSafeClassB(final ClassB obj) {
       // Work out if safe to run and then return true/fals 
    }

}

public interface InterfaceA {
   void run()
}

public class ClassA implements InterfaceA {
   public void run() {
      // implements method here
   }
}

public class ClassB implements InterfaceA {
    public void run() {
       // implements method here
    }
}

次に、次のような JUnit テストを作成します。

@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class})
public class ClassToTestTest {
    private final ClassToTest tester = new ClassToTest();

    @Test
    public void test() {
        MockGateway.MOCK_GET_CLASS_METHOD = true;
        final ClassA classA = spy(new ClassA());
        doReturn(ClassA.class).when(classA).getClass();
        MockGateway.MOCK_GET_CLASS_METHOD = false;

        tester.testMethod(classA);
        verify(classA).run();
    }
}

私の問題は、test() メソッド classA.getClass(); の中にあることです。ClassA を返します。テスターの testMethod() メソッド内に入ると、 ClassA$EnhancerByMockitoWithCGLIB$... クラスが返されるため、有用なオブジェクトは常に null になります。

クラスのモックを回避する方法はありますか、またはこれを修正するために何をする必要がありますか?

前もって感謝します。

4

3 に答える 3

5

あなたの問題は実際にgetClassfinalObjectあるため、Mockito でスタブすることはできません。これを回避する良い方法が思い浮かびません。考えられる可能性が 1 つあります。

単一のメソッドを持つユーティリティ クラスを作成する

public Class getClass(Object o){
    return o.getClass();
}

テストしているクラスをリファクタリングして、直接呼び出すのではなく、このユーティリティ クラスのオブジェクトを使用するようにしますgetClass()。次に、特別なパッケージ プライベート コンストラクターまたはセッター メソッドを使用して、ユーティリティ オブジェクトを注入できるようにします。

public class ClassToTest{
    private UtilityWithGetClass utility;
    private Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();

    public ClassToTest() {
        this(new UtilityWithGetClass());
    }

    ClassToTest(UtilityWithGetClass utility){
        this.utility = utility;
        // Populate map here
    }

    // more stuff here
}

次に、テストで、オブジェクトとスタブのモックを作成しますgetClass。テストするクラスにモックを挿入します。

于 2012-09-03T20:27:16.360 に答える
4

うわー、このコードをテスト可能にするのはなんて頭痛の種でしょう。key主な問題は、モック オブジェクトをオブジェクトとしてへの呼び出しに使用できないことと、テストのためにオブジェクトをモックmap.get(obj.getClass())しようとしている可能性があることです。invoke()機能/動作をモックアウトしてその動作を検証できるように、テスト対象のクラスをリファクタリングする必要がありました。

したがって、これは、さまざまな機能を分離し、テスト クラスによって注入されるメンバー変数でテストされる新しい実装です。

public class ClassToTest {

    MethodStore methodStore;
    MethodInvoker methodInvoker;
    ClassToInvoke classToInvoke;
    ObjectRunner objectRunner;  

    public void testMethod(InterfaceA obj) throws Exception {

        Method method = methodStore.getMethod(obj);

        boolean ok = false;

        if (method != null) {
            ok = methodInvoker.invoke(method, classToInvoke, obj);
        }

        if (ok) {
            objectRunner.run(obj);
        }   
    }

    public void setMethodStore(MethodStore methodStore) {
        this.methodStore = methodStore;
    }

    public void setMethodInvoker(MethodInvoker methodInvoker) {
        this.methodInvoker = methodInvoker;
    }

    public void setObjectRunner(ObjectRunner objectRunner) {
        this.objectRunner = objectRunner;
    }

    public void setClassToInvoke(ClassToInvoke classToInvoke) {
        this.classToInvoke = classToInvoke;
    }
}

これは、メソッド クラスをモックできないため、PowerMock を必要としなくなったテスト クラスです。NullPointerException を返すだけです。

public class MyTest {

    @Test
    public void test() throws Exception {

        ClassToTest classToTest = new ClassToTest();

        ClassA inputA = new ClassA();

        // trying to use powermock here just returns a NullPointerException
//      final Method mockMethod = PowerMockito.mock(Method.class);
        Method mockMethod = (new ClassToInvoke()).getClass().getMethod("someMethod");   // a real Method instance

        // regular mockito for mocking behaviour    
        ClassToInvoke mockClassToInvoke = mock(ClassToInvoke.class);
        classToTest.setClassToInvoke(mockClassToInvoke);

        MethodStore mockMethodStore = mock(MethodStore.class);
        classToTest.setMethodStore(mockMethodStore);

        when(mockMethodStore.getMethod(inputA)).thenReturn(mockMethod);

        MethodInvoker mockMethodInvoker = mock(MethodInvoker.class);
        classToTest.setMethodInvoker(mockMethodInvoker);

        when(mockMethodInvoker.invoke(mockMethod,mockClassToInvoke, inputA)).thenReturn(Boolean.TRUE);

        ObjectRunner mockObjectRunner = mock(ObjectRunner.class);
        classToTest.setObjectRunner(mockObjectRunner);

        // execute test method      
        classToTest.testMethod(inputA);

        verify(mockObjectRunner).run(inputA);   
    }
}

必要な追加クラスは次のとおりです。

public class ClassToInvoke {
    public void someMethod() {};
}

public class ClassA implements InterfaceA {

    @Override
    public void run() {
        // do something
    }
}

public class ClassToInvoke {
    public void someMethod() {};
}

public class MethodInvoker {

    public Boolean invoke(Method method, Object obj, InterfaceA a) throws Exception {
         return (Boolean) method.invoke(obj, a);
    }
}

public class MethodStore {

    Map<Class<?>, Method> map = new HashMap<Class<?>, Method>();

    public Method getMethod(InterfaceA obj) {
        return map.get(obj);
    }
}

これらすべてを IDE に入れると、緑色のバーが表示されます...うわー!

于 2012-09-03T23:31:48.287 に答える