Mockito - @间谍vs @模仿

zdwk9cvp  于 2022-11-08  发布在  其他
关注(0)|答案(9)|浏览(226)

我知道一个间谍调用一个对象上的真实方法,而一个模拟调用一个双精度对象上的方法。同样间谍是要避免的,除非有代码的味道。但是,间谍是如何工作的,我应该在什么时候真正使用它们?它们和模拟有什么不同?

ymzxtsji

ymzxtsji1#

从技术上讲,“模拟”和“间谍”都是一种特殊的“测试替身”。
不幸的是,Mockito让这种区别变得很奇怪。

mockito中的mock是其他mock框架中的普通mock(允许您存根调用;即从方法调用中返回特定值)。
mockito中的spy是其他模拟框架中的部分模拟(对象的一部分将被模拟,一部分将使用真正的方法调用)。

0wi1tuuw

0wi1tuuw2#

两者都可以用来模拟方法或字段。不同的是,在模拟中,你创建了一个完整的模拟或伪对象,而在间谍中,有一个真正的对象,你只是间谍或存根它的特定方法。
当然,在spy对象中,由于它是一个真正的方法,当你不存根方法时,它将调用真正的方法行为。如果你想改变和模拟方法,那么你需要存根它。
请将下面的示例作为比较。

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?如果你想安全,避免调用外部服务,只想测试单元内部的逻辑,那么就使用mock。如果你想调用外部服务,执行真正依赖的调用,或者简单地说,你想运行程序,只是存根特定的方法,那么就使用spy。这就是mockito中spy和mock的区别。

cwxwcias

cwxwcias3#

简而言之:
@Spy@Mock在代码测试中被大量使用,但是开发人员在使用它们中的一个的情况下确实会混淆,因此开发人员最终使用@Mock是安全的。

  • 如果您只想在外部测试功能,而不想实际调用该方法,请使用@Mock
  • 当您想要在外部+内部使用所呼叫的方法来测试功能时,请使用@Spy

下面是我在美国Election 20 xx的场景的例子。
选民可以按照VotersOfBelow21VotersOfABove21进行划分。
理想的出口民调显示,特朗普将赢得选举,因为VotersOfBelow21VotersOfABove21都将投票给特朗普,说“我们选举了特朗普总统

但这不是真实的场景:

这两个年龄段的选民都投票给了特朗普,因为除了特朗普,他们没有其他有效的选择。

如何测试它

第一个
请注意,在上面的前两类中,这两个年龄组的人都说他们没有比特朗普更好的选择。这明确地意味着他们投票给特朗普只是因为他们别无选择。
现在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
zynd9foi

zynd9foi4#

TL;DR版本,
使用mock,它将为您创建一个基本shell示例。

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

使用spy,您可以部分模拟现有示例

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

Spy的典型使用情形:类有一个参数化的构造函数,那么您需要首先创建对象。

am46iovg

am46iovg5#

我在这里创建了一个runable示例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);
}

你可能不需要间谍,因为你可以只模仿存款和取款服务。
但是对于一些遗留代码,依赖关系在代码中是这样的:

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

更多详情请点击上面的链接。

j0pj023g

j0pj023g6#

最好的起点可能是the docs for mockito
一般来说,mockito mock允许您创建存根。
例如,如果一个stub方法执行一个开销很大的操作,你就需要创建一个stub方法。比如说,它获取一个数据库连接,从数据库中检索一个值,然后将其返回给调用者。获取数据库连接可能需要30秒,这会减慢你的测试执行速度,以至于你可能会进行上下文切换(或者停止运行测试)。
如果您正在测试的逻辑不关心数据库连接,那么您可以用一个返回硬编码值的stub来替换该方法。
mockito spy可以让你检查一个方法是否调用了其他方法。这在尝试测试遗留代码时非常有用。
如果你在测试一个可以通过副作用来工作的方法,那么你可以使用mockito spy。它将调用委托给真实的对象,并允许你验证方法调用,调用的次数等等。

slwdgvem

slwdgvem7#

mock用于模拟一个类的 * 所有 * 方法。
spy用于模拟 * 某些 * 方法,对于其余方法,必须进行实际调用。

kpbwa7wx

kpbwa7wx8#

我喜欢这个建议的简洁性:

  • 如果您希望安全,避免调用外部服务,并且只想测试单元内部的逻辑,那么请使用mock
  • 如果你想调用外部服务并执行真正依赖项的调用,或者简单地说,你想运行程序,只是存根特定的方法,那么使用spy

来源:https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/
一个共同的区别是:

  • 如果你想直接stub一个依赖项的方法,那么Mock那个依赖项。
  • 如果您希望存根依赖项中的数据,以便其所有方法返回所需的测试值,则Spy该依赖项。
fivyi3re

fivyi3re9#

这个节目有点晚了,但是我觉得其他的回答并没有很好地说明间谍和模仿之间的区别。
给定要测试的服务

public class EmailService {

public String sendMail(String email) {
    return String.format("Email successfully sent to %s", email);
   }
}

我们现在可以用四种不同的场景进行一些测试。
1.呼叫并停止模拟
1.调用模拟而不存根
1.不打电话就能叫间谍
1.打电话干掉一个间谍
设定:

private final String testEmail = "randomuser@domain.com";
private final String success = "SUCCESS";
@Mock EmailService emailService;
@Spy EmailService emailServiceSpy;

测试项目:

@Test
@Description("When mock is called, we can return any response we like")
public void simpleTest1() {

    when(emailService.sendMail(testEmail)).thenReturn(success);
    assertEquals(success, emailService.sendMail(testEmail));
}

@Test
@Description("When mock is called but not stubbed, we receive a null value")
public void simpleTest2() {
    assertNull(emailService.sendMail(testEmail));
}

@Test
@Description("When a spy is called but not stubbed, the concrete impl is called")
public void simpleTest3() {
    assertTrue(emailServiceSpy.sendMail(testEmail).contains(testEmail));
}

@Test
@Description("When a spy is called and stubbed, stubbed value is returned")
public void simpleTest4() {
    when(emailServiceSpy.sendMail(testEmail)).thenReturn(success);
    assertEquals(success, emailServiceSpy.sendMail(testEmail));
}

Mock如果没有被stubbed,将返回一个null值,而Spy如果没有被stubbed,将调用具体类内部的实现方法。

相关问题