programing

Mockito를 사용한 클래스의 멤버 변수 모킹

newsource 2022. 8. 25. 00:02

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()이런 수업.나는 이것을 하기 위해 모키토를 이용하고 있다.

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

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

나는 조롱이 효과가 없고 주장이 실패하는 것을 보고 있다.테스트하고 싶은 클래스의 멤버 변수를 조롱할 방법은 없습니까?

멤버 변수에 액세스하는 방법을 제공해야 모크를 전달할 수 있습니다(가장 일반적인 방법은 매개 변수를 사용하는 세터 메서드 또는 생성자).

코드가 이 방법을 제공하지 않는 경우 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테스트의 속성은 여전히Second(모크를 넘겨주지 않음)first를 참조해 주세요).

가장 간단한 방법은 세터를 작성하는 것입니다.secondFirst수업하고 확실하게 모의고사를 통과해야 합니다.

다음과 같이 합니다.

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());
}

또 하나는 A에 합격하는 것이다.Second로서 인스턴스화하다.First의 컨스트럭터 파라미터.

코드를 수정할 수 없는 경우에는 reflection을 사용하는 방법밖에 없다고 생각합니다.

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);

여기서 문제는 새로운 Second에 대한 모든 호출이 동일한 모의 인스턴스를 반환한다는 것입니다.하지만 당신의 간단한 경우에는 이것이 효과가 있을 것이다.

Mockito는 슈퍼 컨스트럭터를 호출하지 않기 때문에 프라이빗 값이 설정되지 않은 것과 같은 문제가 있었습니다.반성을 통해 조롱을 증가시키는 방법이 여기 있다.

먼저 Test Utils 클래스를 만들었습니다.이 클래스에는 이러한 반사 메서드를 포함한 많은 유용한 유틸리티가 포함되어 있습니다.리플렉션액세스는 매번 실장하기에는 다소 번거롭습니다.저는 이런저런 이유로 조롱 패키지가 없는 프로젝트에서 코드를 테스트하기 위해 이 방법을 개발했습니다.또한 이 방법을 포함하도록 초대받지 않았습니다.

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
}

실제 프로젝트의 코드를 페이지 단위로 수정했습니다.컴파일에 문제가 있을 수 있습니다.대충 아시겠죠?도움이 된다면 언제든지 코드를 잡고 사용하세요.

모크를 사용하여 할 수 .ReflectionTestUtils

ReflectionTestUtils.setField(yourMock, "memberFieldName", value);

Reflection을 대체하고 싶은 경우봄부터의 TestUtils mockito, 사용

Whitebox.setInternalState(first, "second", sec);

다른 많은 사람들은 이미 코드를 더 테스트하기 쉽게 하기 위해 다시 생각해보라고 충고했습니다. 좋은 조언이고 보통 제가 지금 제안하는 것보다 더 간단합니다.

테스트하기 쉽게 코드를 변경할 수 없는 경우 PowerMock: https://code.google.com/p/powermock/

PowerMock은 Mockito를 확장하여 새로운 모의 프레임워크를 배울 필요가 없습니다.추가 기능을 제공합니다.여기에는 컨스트럭터가 모크를 반환하도록 하는 기능이 포함됩니다.강력하지만 조금 복잡합니다. 그러니 현명하게 사용하세요.

다른 모의 주자를 사용합니다.또한 생성자를 호출할 클래스를 준비해야 합니다.(이것은 일반적인 gotcha입니다.생성된 클래스가 아니라 생성자를 호출하는 클래스를 준비합니다.)

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

그런 다음 테스트 셋업에서 whenNew 메서드를 사용하여 컨스트럭터가 모크를 반환하도록 할 수 있습니다.

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

네, 다음 테스트에서 알 수 있듯이 (제가 개발한 JMockit 모의 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에서 조롱이 구현되는 방식에 기인합니다.이 "mock" 서브클래스의 인스턴스만 조롱된 동작을 가질 수 있으므로 테스트된 코드가 다른 인스턴스 대신 이들을 사용하도록 해야 합니다.

언급URL : https://stackoverflow.com/questions/8995540/mocking-member-variables-of-a-class-using-mockito