Mockito - 间谍vs模拟

时间:2015-02-03 09:38:15

标签: java unit-testing mocking mockito spy

Mockito - 我理解间谍调用对象上的真实方法,而模拟调用double对象上的方法。除非有代码气味,否则应避免使用间谍。 但是,间谍如何工作以及我何时应该使用它们? 它们与模拟有什么不同?

7 个答案:

答案 0 :(得分:63)

从技术上讲,“嘲笑”和“间谍”都是一种特殊的“测试双打”。

不幸的是,Mockito的区别很奇怪。

mockito中的模拟是其他模拟框架中的普通模拟(允许您存根调用;即,从方法调用返回特定值)。

在mockito中的间谍是其他模拟框架中的部分模拟(对象的一部分将被模拟,部分将使用真实的方法调用)。

答案 1 :(得分:11)

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

然后你就可以像这样使用spy注入依赖:

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

上面链接中的更多细节。

答案 2 :(得分:10)

TL; DR版本,

使用 mock ,它会为您创建一个裸骨shell实例。

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

使用间谍,您可以部分模拟现有实例

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

Spy的典型用例:该类具有参数化构造函数,您希望首先创建该对象。

答案 3 :(得分:9)

最好的起点可能是the docs for mockito

总的来说,mockito mock允许您创建存根。

例如,如果该方法执行昂贵的操作,则会创建存根方法。比如说,它获取数据库连接,从数据库中检索一个值并将其返回给调用者。获取数据库连接可能需要30秒,这会将测试执行速度降低到可能上下文切换(或停止运行测试)的程度。

如果您正在测试的逻辑不关心数据库连接,那么您可以使用返回硬编码值的存根替换该方法。

mockito spy允许您检查方法是否调用其他方法。在尝试获取遗留代码时,这非常有用。

如果你正在测试一种通过副作用起作用的方法,那么你会使用模拟间谍。这会委托对真实对象的调用,并允许您验证方法调用,调用的次数等。

答案 4 :(得分:9)

两者都可用于模拟方法或字段。不同的是,在mock中,你正在创建一个完整的模拟或假对象,而在间谍中,有真实的对象,你只是间谍或抄袭它的具体方法。

当然,在间谍对象中,因为它是一种真正的方法,当你没有对方法进行存根时,它会调用真正的方法行为。如果要更改并模拟方法,则需要将其存根。

考虑以下示例作为比较。

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。如果你想调用外部服务并执行真正依赖的调用,或者只是说,你想按原样运行程序,只是存根特定的方法,那么使用spy。这就是在mockito中间谍和模拟之间的区别。

答案 5 :(得分:8)

简而言之:

@Spy@Mock在代码测试中大量使用,但是开发人员在使用其中之一的情况下确实感到困惑,因此开发人员最终为了安全起见最终使用@Mock

  • 要只外部测试功能,请使用@Mock 而不实际调用该方法。
  • 要在外部测试功能时使用@Spy, 内部并调用方法。

下面是我在美国采用 Election20xx 的示例。

投票可以根据VotersOfBelow21VotersOfABove21进行划分。

在理想的出口民调说,特朗普将在竞选中获胜,因为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说特朗普胜了,因为两个年龄段的人都以压倒性多数投票支持他。

如果我们要使用@Mock测试ElectionOfYear20XX,那么我们可能无法获得特朗普获胜的真实原因,我们将仅测试外部原因。

如果我们使用@Spy测试ElectionOfYear20XX,那么我们就知道特朗普赢得外部退出民意测验结果(即内部+外部)的真正原因。


我们的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 :(得分:1)

我喜欢此建议的简单性:

  
      
  • 如果您想安全起见,避免调用外部服务,而只是想测试设备内部的逻辑,请使用 mock
  •   
  • 如果您要调用外部服务并执行实际依赖项的调用,或者简单地说,您想按原样运行该程序并仅存入特定方法,请使用 spy
  •   

来源:https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

一个共同的区别是:

  
      
  • 如果您想直接对依赖项的方法进行存根,则模拟该依赖项。
  •   
  • 如果要对依赖项中的数据进行存根,以便其所有方法都返回所需的测试值,请间谍该依赖项。
  •   
相关问题