间谍 vs

Mockito ——我知道一个间谍调用对象的真正方法,而一个 mock 调用双对象的方法。除非有密码的味道,否则也要避免间谍。 然而,间谍是如何工作的,我应该在什么时候使用他们? 它们与模拟有什么不同?

154942 次浏览

最好从 医生说是莫奇托开始。

一般来说,模拟模拟允许您创建存根。

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

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

该间谍允许您检查一个方法是否调用其他方法。这在尝试获取测试中的遗留代码时非常有用。

它是有用的,如果你正在测试一个方法,通过副作用工作,那么你会使用一个仿真间谍。这个委托调用实际对象,并允许您验证方法调用、调用次数等。

从技术上讲,“模仿”和“间谍”都是一种特殊的“试替身”。

不幸的是,莫基托让这个区别变得很奇怪。

Mocito 中的 mock 在其他 Mocito 框架中是一个普通的 mock (允许您存根调用; 也就是说,从方法调用中返回特定的值)。

Mocito 中的间谍是其他 Mocking 框架中的一部分 mock (对象的一部分将被模拟,而另一部分将使用真正的方法调用)。

我在这里创建了一个可运行的例子 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();
}

然后你可以像下面这样使用间谍注入依赖:

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

在上面的链接中有更多的细节。

DR 版本,

使用 嘲笑,它会为您创建一个基本的 shell 实例。

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

使用 间谍,您可以对 存在实例进行部分模拟

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

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。如果您想要调用外部服务并执行对实际依赖项的调用,或者简单地说,您想要按原样运行程序并只是存根特定的方法,那么就使用 span。这就是间谍和嘲笑的区别。

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

  • 如果您希望安全并避免调用外部服务,只是想测试单元内部的逻辑,那么可以使用 嘲笑
  • 如果您想要调用外部服务并执行对实际依赖项的调用,或者简单地说,您想要按原样运行程序并只是存根特定的方法,那么使用 间谍

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

一个共同的区别是:

  • 如果希望直接存根依赖项的方法,那么应该使用该依赖项的 嘲笑
  • 如果希望在依赖项中存根数据,以便其所有方法返回您需要的测试值,则使用该依赖项的 间谍

简而言之:

@Spy@Mock在代码测试中被大量使用,但是开发人员在何时使用它们中的一个时会混淆,因此开发人员最终使用 @Mock来保证安全。

  • 当您只想在外部测试功能 < strong > 时,可以使用 @Mock 而不实际调用那个方法。
  • 当您想要测试功能 < strong > foreign + 时,请使用 @Spy 在内部 ,并且正在调用这个方法

下面的例子是我在美国采取的 选举20xx的情况。

选民可以根据 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

如果我们想模拟 所有类的方法,则使用 mock

如果我们想要模拟 一些方法,并且对于其余方法,必须进行实际调用,则使用 spy

这个节目有点晚了,但是我觉得其他的回复并不能很好地说明间谍和模仿者之间的区别。这是一个小小的演示

提供服务进行测试

public class EmailService {


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

我们现在可以对四种不同的场景进行一些测试。

  1. 打电话给一个嘲笑我的人
  2. 调用一个没有撞击的嘲笑
  3. 打电话给一个间谍
  4. 给间谍打电话,干掉他

设置:

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 将返回空值,而 Spy 如果没有存根化,将调用具体类中的实现方法。