[java] Mockito를 사용하여 클래스의 모의 멤버 변수

나는 특히 개발과 단위 테스트에 초보자입니다. 내 요구 사항이 매우 간단하다고 생각하지만 다른 사람들의 생각을 알고 싶어합니다.

내가 두 개의 클래스를 가지고 있다고 가정 해보십시오.

public class First {

    Second second ;

    public First(){
        second = new Second();
    }

    public String doSecond(){
        return second.doSecond();
    }
}

class Second {

    public String doSecond(){
        return "Do Something";
    }
}

First.doSecond()방법 을 테스트 하기 위해 단위 테스트를 작성한다고 가정 해 봅시다 . 그러나 Second.doSecond()클래스 를 Mock하고 싶습니다. Mockito를 사용 하여이 작업을 수행하고 있습니다.

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

조롱이 적용되지 않고 어설 션이 실패한다는 것을 알았습니다. 테스트하려는 클래스의 멤버 변수를 조롱 할 수있는 방법이 없습니까? ?



답변

모의를 전달할 수 있도록 멤버 변수에 액세스하는 방법을 제공해야합니다 (가장 일반적인 방법은 setter 메소드 또는 매개 변수를 취하는 생성자입니다).

코드에서이를 수행하는 방법을 제공하지 않으면 TDD (Test Driven Development)에 대해 잘못 고려됩니다.


답변

코드를 변경할 수 없으면 불가능합니다. 그러나 의존성 주입을 좋아하고 Mockito가 지원합니다.

public class First {
    @Resource
    Second second;

    public First() {
        second = new Second();
    }

    public String doSecond() {
        return second.doSecond();
    }
}

당신의 시험 :

@RunWith(MockitoJUnitRunner.class)
public class YourTest {
   @Mock
   Second second;

   @InjectMocks
   First first = new First();

   public void testFirst(){
      when(second.doSecond()).thenReturn("Stubbed Second");
      assertEquals("Stubbed Second", first.doSecond());
   }
}

이것은 매우 좋고 쉽습니다.


답변

코드를 자세히 보면 second테스트 의 속성이 여전히 Secondmock이 아닌 의 인스턴스 라는 것을 알 수 first있습니다 (코드에서 mock을 전달하지 않음 ).

가장 간단한 방법은 클래스 second에서 setter를 만들고 First모의 객체를 명시 적으로 전달하는 것입니다.

이처럼 :

public class First {

Second second ;

public First(){
    second = new Second();
}

public String doSecond(){
    return second.doSecond();
}

    public void setSecond(Second second) {
    this.second = second;
    }


}

class Second {

public String doSecond(){
    return "Do Something";
}
}

....

public void testFirst(){
Second sec = mock(Second.class);
when(sec.doSecond()).thenReturn("Stubbed Second");


First first = new First();
first.setSecond(sec)
assertEquals("Stubbed Second", first.doSecond());
}

다른 하나는 Second인스턴스를 First생성자 매개 변수 로 전달하는 것 입니다.

코드를 수정할 수 없다면 리플렉션을 사용하는 것이 유일한 옵션이라고 생각합니다.

public void testFirst(){
    Second sec = mock(Second.class);
    when(sec.doSecond()).thenReturn("Stubbed Second");


    First first = new First();
    Field privateField = PrivateObject.class.
        getDeclaredField("second");

    privateField.setAccessible(true);

    privateField.set(first, sec);

    assertEquals("Stubbed Second", first.doSecond());
}

그러나 제어 할 수없는 코드에 대한 테스트는 드물기 때문에 아마도 가능할 수 있습니다 (하지만 외부 라이브러리를 테스트 해야하는 시나리오는 저자가하지 않은 원인을 상상할 수는 있음))


답변

멤버 변수를 변경할 수 없으면 다른 방법으로 powerMockit을 사용하고

Second second = mock(Second.class)
when(second.doSecond()).thenReturn("Stubbed Second");
whenNew(Second.class).withAnyArguments.thenReturn(second);

이제 문제는 new Second를 호출하면 동일한 조롱 된 인스턴스가 반환된다는 것입니다. 그러나 간단한 경우에는 효과가 있습니다.


답변

Mockito가 슈퍼 생성자를 호출하지 않기 때문에 개인 값이 설정되지 않은 것과 동일한 문제가있었습니다. 반사를 사용하여 조롱하는 방법을 설명합니다.

먼저, 이러한 리플렉션 메소드를 포함하여 많은 유용한 유틸리티를 포함하는 TestUtils 클래스를 작성했습니다. 리플렉션 액세스는 매번 구현하기에 약간 힘듭니다. 어떤 이유로 든 모의 패키지가 없으며 포함하도록 초대되지 않은 프로젝트에서 코드를 테스트하기 위해 이러한 방법을 만들었습니다.

public class TestUtils {
    // get a static class value
    public static Object reflectValue(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(classToReflect, fieldNameValueToFetch);
            reflectField.setAccessible(true);
            Object reflectValue = reflectField.get(classToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // get an instance value
    public static Object reflectValue(Object objToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameValueToFetch);
            Object reflectValue = reflectField.get(objToReflect);
            return reflectValue;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch);
        }
        return null;
    }
    // find a field in the class tree
    public static Field reflectField(Class<?> classToReflect, String fieldNameValueToFetch) {
        try {
            Field reflectField = null;
            Class<?> classForReflect = classToReflect;
            do {
                try {
                    reflectField = classForReflect.getDeclaredField(fieldNameValueToFetch);
                } catch (NoSuchFieldException e) {
                    classForReflect = classForReflect.getSuperclass();
                }
            } while (reflectField==null || classForReflect==null);
            reflectField.setAccessible(true);
            return reflectField;
        } catch (Exception e) {
            fail("Failed to reflect "+fieldNameValueToFetch +" from "+ classToReflect);
        }
        return null;
    }
    // set a value with no setter
    public static void refectSetValue(Object objToReflect, String fieldNameToSet, Object valueToSet) {
        try {
            Field reflectField  = reflectField(objToReflect.getClass(), fieldNameToSet);
            reflectField.set(objToReflect, valueToSet);
        } catch (Exception e) {
            fail("Failed to reflectively set "+ fieldNameToSet +"="+ valueToSet);
        }
    }

}

그런 다음 이와 같은 개인 변수로 클래스를 테스트 할 수 있습니다. 이것은 제어 할 수없는 클래스 트리 깊이를 조롱하는 데 유용합니다.

@Test
public void testWithRectiveMock() throws Exception {
    // mock the base class using Mockito
    ClassToMock mock = Mockito.mock(ClassToMock.class);
    TestUtils.refectSetValue(mock, "privateVariable", "newValue");
    // and this does not prevent normal mocking
    Mockito.when(mock.somthingElse()).thenReturn("anotherThing");
    // ... then do your asserts
}

페이지의 실제 프로젝트에서 코드를 수정했습니다. 컴파일 문제가있을 수 있습니다. 나는 당신이 일반적인 생각을 얻는다고 생각합니다. 유용하다고 생각되면 코드를 잡고 사용하십시오.


답변

많은 사람들이 이미 코드를 다시 테스트하여 더 테스트 가능하게 만들 것을 권고했습니다. 좋은 조언이며 일반적으로 내가 제안하려는 것보다 간단합니다.

더 테스트 가능하도록 코드를 변경할 수없는 경우 PowerMock : https://code.google.com/p/powermock/

PowerMock은 Mockito를 확장하여 (새로운 모의 프레임 워크를 배울 필요가 없음) 추가 기능을 제공합니다. 여기에는 생성자가 모형을 반환하도록하는 기능이 포함됩니다. 강력하지만 약간 복잡하므로 신중하게 사용하십시오.

다른 Mock 러너를 사용합니다. 그리고 생성자를 호출 할 클래스를 준비해야합니다. (이것은 일반적인 문제입니다-생성 된 클래스가 아닌 생성자를 호출하는 클래스를 준비하십시오)

@RunWith(PowerMockRunner.class)
@PrepareForTest({First.class})

그런 다음 테스트 설정에서 whenNew 메소드를 사용하여 생성자가 모형을 반환하도록 할 수 있습니다

whenNew(Second.class).withAnyArguments().thenReturn(mock(Second.class));


답변

예, 다음 테스트 쇼 (개발 한 JMockit mocking API로 작성 됨)와 같이 수행 할 수 있습니다.

@Test
public void testFirst(@Mocked final Second sec) {
    new NonStrictExpectations() {{ sec.doSecond(); result = "Stubbed Second"; }};

    First first = new First();
    assertEquals("Stubbed Second", first.doSecond());
}

그러나 Mockito를 사용하면 이러한 테스트를 작성할 수 없습니다. 이것은 Mockito에서 조롱이 구현되는 방식 때문인데, 조롱 할 클래스의 서브 클래스가 생성됩니다. 이 “mock”서브 클래스의 인스턴스 만이 모의 동작을 가질 수 있으므로 테스트 된 코드가 다른 인스턴스 대신 사용하도록해야합니다.