我想测试我的方法IOConsoleWriterImpl.displayAllClientsInfo(List<ClientEntity> clients)
它打印数据。
displayAllClientsInfo
大致具有以下结构:
for (ClientEntity client : clients) {
System.out.println(client.getName());
List<AccountEntity> accounts = client.getAccountEntities();
for (AccountEntity account : accounts){
System.out.println(account.getLogin());
}
}
所以,据我了解,为了模拟两个 foreach 循环(使用 Mockito),我需要模拟两个迭代器。
有测试:
@Test
void isDisplayAllClientsInfoPrintData() {
//Given
List<ClientEntity> clients = mock(List.class);
List<AccountEntity> accounts = mock(List.class);
Iterator<ClientEntity> clientIterator = mock(Iterator.class);
Iterator<AccountEntity> accountIterator = mock(Iterator.class);
ClientEntity client = mock(ClientEntity.class);
AccountEntity account = mock(AccountEntity.class);
when(clientIterator.hasNext()).thenReturn(true, false);
when(clientIterator.next()).thenReturn(client);
when(clients.iterator()).thenReturn(clientIterator);
when(accountIterator.hasNext()).thenReturn(true, false);
when(accountIterator.next()).thenReturn(account);
when(accounts.iterator()).thenReturn(accountIterator);
when(clients.size()).thenReturn(1);
when(client.getAccountEntities()).thenReturn(accounts);
when(client.getId()).thenReturn(1L);
when(client.getEmail()).thenReturn("client@example.com");
when(client.getName()).thenReturn("John Smith");
when(account.getId()).thenReturn(2L);
when(account.getCreated()).thenReturn(LocalDateTime.of(2017,5,25,12,59));
when(account.getLogin()).thenReturn("JSmith");
when(account.getPassword()).thenReturn("zzwvp0d9");
//When
IOConsoleWriter io = new IOConsoleWriterImpl();
io.displayAllClientsInfo(clients);
//Then
String output = outputStream.toString();
assertAll(
() -> assertTrue(output.contains(Long.toString(1))),
() -> assertTrue(output.contains("client@example.com")),
() -> assertTrue(output.contains("John Smith")),
() -> assertTrue(output.contains(Long.toString(2))),
() -> assertTrue(output.contains(LocalDateTime.of(2017,5,25,12,59).toString())),
() -> assertTrue(output.contains("JSmith")),
() -> assertTrue(output.contains("zzwvp0d9"))
);
}
我相信有一种很好的方法可以避免代码重复(我的意思是测试的第二段和第三段)。或者我不应该担心,一切都很好?
这一切看起来有点尴尬,所以我可以理解你对如何简化它的想法。
您可以只传入ClientInfo
实例的实际列表,而不是模拟实例。例如:
List<ClientInfo> clientInfos = new ArrayList<>();
clients.add(new ClientInfo(1L, "client@example.com", "John Smith",
Arrays.asList(
new Account(2L, LocalDateTime.of(2017,5,25,12,59), "JSmith", "zzwvp0d9"))
)
);
io.displayAllClientsInfo(clients);
但这似乎是显而易见的,所以也许有一些原因你还没有这样做(也许构造这些类有点尴尬或过于冗长)。
或者,您可以通过使代码对测试更友好来避免"测试设置"的尴尬。例如,您可以将IOConsoleWriterImpl
中的"写入"责任提取到注入IOConsoleWriterImpl
的接口中。像这样:
// extract from IOConsoleWriterImpl
public IOConsoleWriterImpl(Writer writer) {
this.writer = writer;
}
public void displayAllClientsInfo(ClientEntity clients) {
for (ClientEntity client : clients) {
System.out.println(client.getName());
List<AccountEntity> accounts = client.getAccountEntities();
for (AccountEntity account : accounts){
writer.write(account.getLogin());
}
}
}
// a new interface to extract the 'writing' behaviour out of IOConsoleWriterImpl
public interface Writer {
void write(String output);
}
// a sysout implementation of the Writer interface
public class SystemOutWriter implements Writer {
@Override
public void write(String output) {
System.out.println(output);
}
}
然后在测试用例中,您可以将模拟Writer
注入IOConsoleWriter
,并验证是否以预期状态调用它。
Writer writer = Mockito.mock(Writer.class);
IOConsoleWriter io = new IOConsoleWriterImpl(writer);
io.displayAllClientsInfo(clients);
Mockito.verify(writer).write(...);
同样,您可以提供Writer
的存根实现,该实现记录给定的内容,然后断言此存根的内容。例如:
public class RecordingWriter implements Writer {
private List<String> recordings = new ArrayList<>();
@Override
public void write(String output) {
recordings.add(output);
}
public boolean contains(String incoming) {
return recordings.contains(incoming);
}
}
RecordingWriter writer = new RecordingWriter();
IOConsoleWriter io = new IOConsoleWriterImpl(writer);
io.displayAllClientsInfo(clients);
assertAll(
() -> assertTrue(writer.contains(Long.toString(1))),
() -> assertTrue(writer.contains("client@example.com")),
() -> assertTrue(writer.contains("John Smith")),
() -> assertTrue(writer.contains(Long.toString(2))),
() -> assertTrue(writer.contains(LocalDateTime.of(2017,5,25,12,59).toString())),
() -> assertTrue(writer.contains("JSmith")),
() -> assertTrue(writer.contains("zzwvp0d9"))
);
更新 1:根据您的评论和您在此处提供的实际课程的链接。
看起来IOConsoleWriterImpl.displayAllClientsInfo()
有两个职责:
- 询问一组
ClientInfo
并计算要打印的内容 - 打印输出(包括标题和格式)
这让我认为提取Writer
接口会提供一些好处:
- 促进
IOConsoleWriterImpl
的SRP - 简化
IOConsoleWriterImpl
- 有助于简化
IOConsoleWriterImpl
测试IOConsoleWriterTest
因为可以只关注客户的审讯责任 - 有助于对"写作"行为进行更简单的测试;你可以写一个只关注作者责任的
SystemOutWriterTest
但是,您已经完成的操作还可以;它提供了良好的IOConsoleWriterImpl.displayAllClientsInfo()
覆盖范围,尽管它非常冗长,但它仍然是可读的。
总之,我建议传入实际列表是最简单的更改,它 (a) 在功能上与您当前拥有的相同,并且 (b) 涉及较少的设置/更易于阅读。除此之外,我关于提取新界面背后的"写作"行为的建议将简化IOConsoleWriterImpl
并使您的测试更加细粒度(每个测试用例可能更小且更容易推理),并且这种更改将 - 我认为 - 非常简单。当然,你对改变的渴望可能不同;)这里的好处不足以要求这种改变。