Mockito를 사용할 때 조롱과 스파이의 차이점은 무엇입니까?


137

Mockito 스파이의 사용 사례는 무엇입니까?

callRealMethod를 사용하여 모든 스파이 유스 케이스를 모의로 처리 할 수있는 것 같습니다.

내가 볼 수있는 한 가지 차이점은 대부분의 메서드 호출을 실제로 원한다면 모의 대 스파이를 사용하기 위해 코드 줄을 절약합니다. 그것이 더 큰 그림입니까?

답변:


100

답은 설명서에 있습니다 .

실제 부분 모형 (1.8.0부터)

마지막으로 메일 링리스트에 대한 많은 내부 토론과 토론 끝에 Mockito에 부분 모의 지원이 추가되었습니다. 이전에는 코드 냄새로 부분 모의를 고려했습니다. 그러나 부분 모의에 대한 합법적 인 사용 사례를 발견했습니다.

릴리스 1.8 이전 spy ()는 실제 부분 모의를 생성하지 않았으며 일부 사용자에게는 혼란 스러웠습니다. 스파이에 대한 자세한 내용은 here 또는 spy (Object) 메소드에 대한 javadoc을 참조하십시오 .

callRealMethod()는 이후에 소개 spy()되었지만 이전 버전과의 호환성을 보장하기 위해 spy ()가 남아 있습니다.

그렇지 않으면, 당신이 옳습니다 : 스파이의 모든 방법은 스텁되지 않는 한 실제입니다. 모의의 모든 메소드 callRealMethod()는 호출 되지 않는 한 스텁됩니다 . 일반적으로 나는 전통적인 대신 관용구 callRealMethod()를 사용하도록 강요하지 않기 때문에을 선호합니다.doXxx().when()when().thenXxx()


이 경우 스파이보다 모의를 선호하는 문제는 클래스에 주입되지 않은 (로컬로 초기화 된) 멤버를 사용하고 나중에 "실제"메소드에 의해 사용되는 경우입니다. 모의에서 멤버는 기본 Java 값으로 초기화되어 잘못된 동작 또는 NullPointerException을 유발할 수 있습니다. 이것을 전달하는 방법은 "init"메소드를 추가 한 다음 "실제로"호출하는 것입니다.
Eyal Roth

문서에서 : "스파이는 예를 들어 레거시 코드를 다룰 때 신중하고 가끔 사용되어야합니다." 단위 테스트 공간은 동일한 방식으로 수행하는 방법이 너무 많습니다.
gdbj

89

스파이와 모의 차이점

Mockito가 모의 객체를 만들 때 – 실제 인스턴스가 아닌 클래스의 클래스에서 모의 ​​객체를 만듭니다. 이 모의는 단순히 클래스의 베어 셸 인스턴스를 생성하고 클래스와의 상호 작용을 추적하기 위해 완전히 계측되었습니다. 반면에 스파이는 기존 인스턴스를 감 쌉니다. 여전히 일반 인스턴스와 동일한 방식으로 작동합니다. 유일한 차이점은 인스턴스와의 모든 상호 작용을 추적하도록 인스트루먼트됩니다.

다음 예제에서 – ArrayList 클래스의 모형을 만듭니다.

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

보시다시피, 모형 목록에 요소를 추가해도 실제로는 아무것도 추가되지 않으며 다른 부작용없이 메소드를 호출하기 만합니다. 반면에 스파이는 다르게 동작합니다. 실제로 add 메소드의 실제 구현을 호출하고 요소를 기본 목록에 추가합니다.

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

size () 메서드를 호출 할 때 크기를 1로 얻지 만이 size () 메서드가 조롱되지 않았기 때문에 객체의 실제 내부 메서드가 호출되었다는 것을 확실히 알 수 있습니다! 그렇다면 1은 어디에서 왔습니까? 내부 실제 size () 메서드는 size ()가 조롱 (또는 스터 빙)되지 않기 때문에 항목이 실제 객체에 추가되었다고 말할 수 있습니다.

출처 : http://www.baeldung.com/mockito-spy + 자기 메모.


1
size ()가 1을 반환한다는 것을 의미하지 않습니까?
검은

첫 번째 예제에서 해당 메소드가 스텁되지 않은 경우 왜 mockedList.size()리턴 0됩니까? 메소드의 리턴 유형이 주어진 것이 기본값입니까?
mike

@mike : Java에서 및 기본값 0을 mockedList.size()반환합니다 . 이후 실행을 시도하면 결과는 동일하게 유지됩니다. intintassertEquals(0, mockedList.size());mockedList.clear();
realPK

2
이 답변은 간단하고 간단하게 작성되었으며 모의와 스파이의 차이점을 마침내 이해하는 데 도움이되었습니다. 좋은데
Pesa5

38

8 개의 메소드가있는 오브젝트가 있고 7 개의 실제 메소드를 호출하고 하나의 메소드를 스텁하려는 테스트가있는 경우 두 가지 옵션이 있습니다.

  1. 모의를 사용하면 7 callRealMethod를 호출하고 하나의 메소드를 스텁하여 설정해야합니다.
  2. 를 사용하면 spy하나의 방법을 스텁하여 설정해야합니다

공식 문서 에 대한 doCallRealMethod부분 모의 객체에 대한 스파이를 사용하는 것이 좋습니다.

부분 모의에 대한 자세한 내용은 javadoc spy (Object)를 참조하십시오. Mockito.spy ()는 부분 모의 객체를 만드는 권장 방법입니다. 그 이유는 spy () 메서드에 전달 된 개체를 생성해야하기 때문에 실제 메서드가 올바르게 구성된 개체에 대해 호출되도록 보장하기 때문입니다.


5

스파이는 레거시 코드에 대한 단위 테스트를 만들 때 유용 할 수 있습니다 .

https://www.surasint.com/mockito-with-spy/ 여기에서 실행 가능한 예제를 만들었으며 여기에 일부를 복사했습니다.

이 코드와 같은 것이 있다면 :

public void transfer(  DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, 
             double amount, String fromAccount, String toAccount){
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

DepositMoneyService와 WithdrawMoneyService를 조롱 할 수 있기 때문에 스파이가 필요하지 않을 수 있습니다.

그러나 일부 레거시 코드에서 종속성은 다음과 같은 코드에 있습니다.

    public void transfer(String fromAccount, String toAccount, double amount){

        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

예, 첫 번째 코드로 변경할 수 있지만 API가 변경됩니다. 이 방법을 여러 곳에서 사용하는 경우 모든 방법을 변경해야합니다.

대안은 다음과 같이 종속성을 추출 할 수 있다는 것입니다.

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }
    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

그런 다음 스파이를 사용하여 다음과 같이 종속성을 주입 할 수 있습니다.

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target).proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target).proxyWithdrawMoneyServiceCreator();

위의 링크에서 자세한 내용을 확인하십시오.


0

Mock베어 더블 오브젝트입니다. 이 객체는 동일한 메소드 서명을 가지고 있지만 실현은 비어 있고 기본값-0과 null을 반환합니다.

Spy복제 된 이중 객체입니다. 새 객체는 실제 객체를 기준으로 복제되지만이를 조롱 할 가능성이 있습니다

class A {

    String foo1() {
        foo2();
        return "RealString_1";
    }

    String foo2() {
        return "RealString_2";
    }

    void foo3() {
        foo4();
    }

    void foo4() {

    }
}
@Test
public void testMockA() {

    //given
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.foo1()).thenReturn("MockedString");

    //when
    String result1 = mockA.foo1();
    String result2 = mockA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals(null, result2);

    //Case 2
    //when
    mockA.foo3();

    //then
    verify(mockA).foo3();
    verify(mockA, never()).foo4();
}

@Test
public void testSpyA() {
    //given
    A spyA = Mockito.spy(new A());

    Mockito.when(spyA.foo1()).thenReturn("MockedString");

    //when
    String result1 = spyA.foo1();
    String result2 = spyA.foo2();

    //then
    assertEquals("MockedString", result1);
    assertEquals("RealString_2", result2);

    //Case 2
    //when
    spyA.foo3();

    //then
    verify(spyA).foo3();
    verify(spyA).foo4();
}

[더블 타입 테스트]

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.