我想测试我的方法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 中的“写入”职责提取到注入(inject)到 IOConsoleWriterImpl 中的接口(interface)中。像这样的事情:
// 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
注入(inject)到 IOConsoleWriter
中,并验证它是否以您预期的状态调用。
Writer writer = Mockito.mock(Writer.class);
IOConsoleWriter io = new IOConsoleWriterImpl(writer);
io.displayAllClientsInfo(clients);
Mockito.verify(writer).write(...);
类似地,您可以提供 Writer
的 stub 实现,它记录所给的内容,然后对该 stub 的内容进行断言。例如:
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:基于您的评论以及您提供的实际类(class)的链接。
看起来IOConsoleWriterImpl.displayAllClientsInfo()
有两个职责:
- 询问
ClientInfo
集合并确定要打印的内容 - 打印输出(包括标题和格式)
这让我认为提取 Writer
接口(interface)会带来一些好处:
- 提升
IOConsoleWriterImpl
的 SRP - 简化了
IOConsoleWriterImpl
- 有利于对
IOConsoleWriterImpl
进行更简单的测试,因为IOConsoleWriterTest
可以只关注客户端询问职责 - 有利于更简单地测试“写入”行为;您可以编写一个
SystemOutWriterTest
,它只关注编写者的职责
但是,你已经完成的事情是可以的;它提供了对 IOConsoleWriterImpl.displayAllClientsInfo() 的良好覆盖,尽管它相当冗长,但仍然可读。
总之,我建议传入实际的列表是最简单的更改,它(a)在功能上与您当前拥有的相同,并且(b)涉及更少的设置/更易于阅读。除此之外,我关于提取新界面背后的“写入”行为的建议将简化 IOConsoleWriterImpl 并使您的测试更加细粒度(每个测试用例可能更小并且更容易推理)并且这个我认为,改变是很容易实现的。当然,您对改变的兴趣可能会有所不同;)并且这里的好处不足以令人信服地要求进行这种改变。
关于java - 如何简化测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46588933/