Mockito-我知道间谍在对象上调用真实方法,而模拟对象在double对象上调用方法。除非有代码气味,否则也应避免间谍活动。但是,间谍如何工作,我什么时候应该实际使用它们?它们与模拟游戏有何不同?
Answers:
两者都可以用来模拟方法或字段。区别在于,在模拟中,您创建的是一个完整的模拟或假对象,而在间谍中,则存在真实的对象,而您只是在监视或存根特定的方法。
当然,在间谍对象中,由于它是一个真实的方法,所以当您不对方法进行存根时,它将调用真实的方法行为。如果要更改和模拟该方法,则需要对它进行存根。
考虑下面的示例作为比较。
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));
}
}
什么时候应该使用模拟或间谍?如果您想确保安全并避免调用外部服务,而只想测试单元内部的逻辑,则可以使用模拟。如果您想调用外部服务并执行实际依赖项的调用,或者简单地说,您想按原样运行该程序并仅存入特定方法,则可以使用spy。这就是模仿和模仿中的间谍与模仿之间的区别。
我在这里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();
在上面的链接中有更多详细信息。
最好的起点可能是嘲笑文档。
总的来说,mockito模拟允许您创建存根。
例如,如果该方法执行昂贵的操作,则将创建该方法。说,它获得数据库连接,从数据库中检索值并将其返回给调用方。获得数据库连接可能需要30秒钟,这会使测试执行速度降低到您可能会进行上下文切换(或停止运行测试)的程度。
如果您要测试的逻辑与数据库连接无关,则可以用存根返回硬编码值的存根替换该方法。
模拟间谍可以让您检查一个方法是否调用其他方法。尝试对遗留代码进行测试时,这可能非常有用。
如果您正在测试一种通过副作用起作用的方法,那将很有用,那么您将使用模拟间谍。这将调用委派给实际对象,并允许您验证方法调用,调用次数等。
简而言之:
@Spy
并且@Mock
在代码测试中被大量使用,但是在使用其中一种代码的情况下,开发人员确实会@Mock
感到困惑,因此开发人员最终会出于安全考虑而使用。
@Mock
时,你只想测试功能外
,而不实际调用该方法。@Spy
当您要使用调用的方法从外部+内部测试功能时使用。以下是我在美国采用Election20xx方案的示例。
选民可以根据VotersOfBelow21
和划分VotersOfABove21
。
理想的出口民调说,特朗普将在竞选中获胜,因为VotersOfBelow21
和VotersOfABove21
两者都将票投给特朗普说:“我们选择特朗普总统”
但这不是真实的情况:
两个年龄段的选民都投票支持特朗普,因为他们除了特朗普之外别无其他有效选择。
那么您如何测试呢?
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
我喜欢此建议的简单性:
- 如果您想确保安全并避免调用外部服务,而只想测试单元内部的逻辑,则可以使用模拟。
- 如果您想调用外部服务并执行实际依赖项的调用,或者简单地说,您想按原样运行该程序并仅存入特定方法,则可以使用spy。
资料来源:https : //javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/
共同的区别是:
- 如果要直接对依赖项的方法进行存根,请模拟该依赖项。
- 如果要对依赖项中的数据进行存根,以便其所有方法都返回所需的测试值,则请监视该依赖项。