Mockito-@Spy 대 @Mock


99

Mockito-스파이는 개체에 대한 실제 메서드를 호출하는 반면 모의 개체는 이중 개체에 대한 메서드를 호출한다는 것을 이해합니다. 또한 코드 냄새가없는 경우 스파이를 피해야합니다. 그러나 스파이는 어떻게 작동하며 실제로 언제 사용해야합니까? 모의와 어떻게 다릅니 까?



답변:


88

기술적으로 말하면 "모의"와 "스파이"는 특별한 종류의 "테스트 복식"입니다.

Mockito는 불행히도 구별을 이상하게 만듭니다.

mockito의 mock 은 다른 mock 프레임 워크 의 일반 mock 입니다 (호출을 스텁 할 수 있습니다. 즉, 메서드 호출에서 특정 값을 반환 할 수 있습니다).

mockito의 스파이는 다른 모의 프레임 워크 의 부분 모의 입니다 (객체의 일부는 모의 되고 일부는 실제 메서드 호출을 사용합니다).


41

둘 다 메서드 또는 필드를 모의하는 데 사용할 수 있습니다. 차이점은 모의에서는 완전한 모의 또는 가짜 개체를 만드는 반면 스파이에서는 실제 개체가 있고 특정 방법을 감시하거나 스터 빙하는 것입니다.

물론 스파이 객체에서는 실제 메서드이기 때문에 메서드를 스터 빙하지 않으면 실제 메서드 동작을 호출합니다. 방법을 변경하고 조롱하려면 스텁이 필요합니다.

아래 예를 비교로 고려하십시오.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
 
import java.util.ArrayList;
import java.util.List;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
 
@RunWith(MockitoJUnitRunner.class)
public class MockSpy {
 
    @Mock
    private List<String> mockList;
 
    @Spy
    private List<String> spyList = new ArrayList();
 
    @Test
    public void testMockList() {
        //by default, calling the methods of mock object will do nothing
        mockList.add("test");

        Mockito.verify(mockList).add("test");
        assertEquals(0, mockList.size());
        assertNull(mockList.get(0));
    }
 
    @Test
    public void testSpyList() {
        //spy object will call the real method when not stub
        spyList.add("test");

        Mockito.verify(spyList).add("test");
        assertEquals(1, spyList.size());
        assertEquals("test", spyList.get(0));
    }
 
    @Test
    public void testMockWithStub() {
        //try stubbing a method
        String expected = "Mock 100";
        when(mockList.get(100)).thenReturn(expected);
 
        assertEquals(expected, mockList.get(100));
    }
 
    @Test
    public void testSpyWithStub() {
        //stubbing a spy method will result the same as the mock object
        String expected = "Spy 100";
        //take note of using doReturn instead of when
        doReturn(expected).when(spyList).get(100);
 
        assertEquals(expected, spyList.get(100));
    }
}

모의 또는 스파이를 사용할 때 소리를 지르십니까? 안전하고 외부 서비스 호출을 피하고 장치 내부의 논리를 테스트하려면 mock을 사용하십시오. 외부 서비스를 호출하고 실제 종속성 호출을 수행하거나 단순히 프로그램을 그대로 실행하고 특정 메서드를 스텁하고 싶다면 스파이를 사용하십시오. 이것이 mockito에서 spy와 mock의 차이점입니다.


좋은 대답이지만 모의 오류에서만 verify ()가 발생하고 여기처럼 @Before setUp () 메서드에서 목록을 초기화하지 않는 한 테스트를 실행하지 않습니다. mockList = mock (ArrayList.class); spyList = spy (ArrayList.class); 여기에 제안 된 모의 및 스파이 주석을 제거합니다. 나는 그것을 시험했고 나의 시험은 지금 통과하고있다.
The_Martian

17

TL, DR 버전,

함께 모의 , 그것은 당신을 위해 베어 뼈 쉘 인스턴스를 만듭니다.

List<String> mockList = Mockito.mock(ArrayList.class);

스파이 를 사용 하면 기존 인스턴스를 부분적으로 모의 할 수 있습니다.

List<String> spyList = Mockito.spy(new ArrayList<String>());

스파이의 일반적인 사용 사례 : 클래스에는 매개 변수가있는 생성자가 있으므로 먼저 개체를 만들고 싶습니다.


14

여기 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();

위 링크에서 더 자세히 알아보세요.


13

시작하기에 가장 좋은 곳은 아마도 mockito 문서 일 것입니다 .

일반적으로 mockito mock을 사용하면 스텁을 만들 수 있습니다.

예를 들어 해당 메서드가 비용이 많이 드는 작업을 수행하는 경우 스텁 메서드를 만듭니다. 예를 들어 데이터베이스 연결을 얻고 데이터베이스에서 값을 검색하여 호출자에게 반환합니다. db 연결을 가져 오는 데 30 초가 걸릴 수 있으므로 컨텍스트 전환 (또는 테스트 실행 중지) 가능성이있는 지점까지 테스트 실행 속도가 느려질 수 있습니다.

테스트중인 로직이 데이터베이스 연결에 관심이 없다면 해당 메서드를 하드 코딩 된 값을 반환하는 스텁으로 대체 할 수 있습니다.

mockito 스파이를 사용하면 메서드가 다른 메서드를 호출하는지 여부를 확인할 수 있습니다. 이것은 레거시 코드를 테스트 할 때 매우 유용 할 수 있습니다.

부작용을 통해 작동하는 방법을 테스트하는 경우 mockito 스파이를 사용하는 것이 유용합니다. 이를 통해 실제 객체에 대한 호출을 위임하고 메서드 호출, 호출 횟수 등을 확인할 수 있습니다.


7

요컨대 :

@Spy@Mock코드의 테스트에 많이 사용하지만, 그들 중 하나를 사용하여 개발자가 사용하여 끝날 때 개발자는 경우에 혼동을하는 @Mock안전 할 수 있습니다.

  • 실제로 해당 메서드를 호출하지 않고 @Mock기능을 외부에서 테스트하려는 경우 사용 합니다 .
  • 호출되는 메서드를 사용하여 외부 + 내부적 으로 @Spy기능을 테스트하려는 경우 사용 합니다 .

아래는 내가 미국에서 Election20xx 시나리오를 찍은 예 입니다.

유권자는 VotersOfBelow21및 에 따라 나눌 수 있습니다 VotersOfABove21.

이상적인 출구 여론 조사 때문에 트럼프가 선거에서 이길 것이라고 말했다 VotersOfBelow21VotersOfABove21모두 비장의 말에 대해 투표 할 것이다 " 우리는 대통령 트럼프를 선출 "

그러나 이것은 실제 시나리오가 아닙니다.

두 연령대의 유권자들은 트럼프 외에 다른 효과적인 선택이 없었기 때문에 트럼프에 투표했습니다.

그래서 어떻게 테스트합니까 ??

public class VotersOfAbove21 {
public void weElected(String myVote){
  System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX ");
}
}

public class VotersOfBelow21 {
  public void weElected(String myVote){
    System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
  }
}

public class ElectionOfYear20XX {
  VotersOfAbove21 votersOfAbove21;
  VotersOfBelow21 votersOfBelow21;
  public boolean weElected(String WeElectedTrump){
    votersOfAbove21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");

    votersOfBelow21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");
    return true;
  }

}

이제 위의 처음 두 클래스에서 두 연령대 사람들은 트럼프보다 더 나은 선택이 없다고 말합니다. 그것은 그들이 선택의 여지가 없었기 때문에 트럼프에게 투표했다는 것을 명시 적으로 의미합니다.

이제 ElectionOfYear20XX 트럼프가 두 연령층 모두 압도적으로 투표했기 때문에 승리했다고 말합니다.

ElectionOfYear20XX@Mock 으로 테스트를한다면 트럼프가이긴 진짜 이유를 알 수 없을 수도 있고 외부 이유 만 테스트 할 것입니다.

ElectionOfYear20XX@Spy로 테스트하면 트럼프가 외부 출구 조사 결과, 즉 내부적으로 + 외부 적으로이긴 진짜 이유를 알 수 있습니다.


우리 ELectionOfYear20XX_Test수업 :

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Mock
  VotersOfBelow21 votersOfBelow21;
  @Mock
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

이것은 로직 테스트 결과, 즉 외부 검사 만 출력 해야 합니다 .

We elected President Trump 
We elected President Trump 

@Spy실제 메서드 호출을 사용 하여 외부 및 내부적으로 테스트 합니다.

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Spy
  VotersOfBelow21 votersOfBelow21;
  @Spy
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

산출:

Voters of above 21 has no Choice Than Thrump in 20XX 
We elected President Trump 
Voters of below 21 has no Choice Than Thrump in 20XX
We elected President Trump 

6

이 추천의 단순함이 마음에 듭니다.

  • 안전하고 외부 서비스 호출을 피하고 장치 내부의 논리를 테스트하려면 mock 을 사용하십시오 .
  • 외부 서비스를 호출하고 실제 종속성 호출을 수행하거나 단순히 프로그램을있는 그대로 실행하고 특정 메서드를 스텁하려면 spy 를 사용하십시오 .

출처 : https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

일반적인 차이점은 다음과 같습니다.

  • 종속성의 메소드를 직접 스텁하려면 해당 종속성 을 Mock하십시오 .
  • 당신이 종속성의 데이터를 스텁하려면 모든 방법은, 당신이 필요로하는 것을 값을 테스트를 돌려주는 있도록 스파이를 종속있다.

Spy 및 Mock은 항상 테스트 대상 시스템이 아니라 종속성을 의미합니다.
leo9r
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.