我该如何简化测试?

时间:2017-10-05 15:04:30

标签: java unit-testing junit mockito

我想测试我打印数据的方法DROP TABLE IF EXISTS schema.yourtable(even temporary tables #...)

IOConsoleWriterImpl.displayAllClientsInfo(List<ClientEntity> clients)大致有以下结构:

displayAllClientsInfo

所以,据我所知,为了模拟两个foreach循环(使用Mockito),我需要模拟两个迭代器。

有测试:

for (ClientEntity client : clients) {
    System.out.println(client.getName());
    List<AccountEntity> accounts = client.getAccountEntities();
    for (AccountEntity account : accounts){
        System.out.println(account.getLogin());
    }
}

我相信有一种避免代码重复的好方法(我的意思是测试的第二和第三段)。或者我不应该担心,一切都很好?

1 个答案:

答案 0 :(得分:2)

这一切看起来有点尴尬,所以我能理解你对如何简化它的想法。

您只需传入{strong>实际 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()的良好覆盖率,虽然它非常详细,但它仍然可读。

总之,我建议传入一个实际的List是最简单的变化,它是(a)功能上与你现在拥有的相同,并且(b)涉及更少的设置/更容易阅读。除此之外,我关于提取新界面背后的“写入”行为的建议将简化IOConsoleWriterImpl并使您的测试更精细(每个测试用例可能更小,更容易推理)并且此更改将 - 我认为 - 制作非常简单。当然,你对变革的兴趣可能会有所不同;)这里的好处并不足以引起这种变化。