如何在实现之前测试 void 方法?

How to test a void method before implementing it?

我正在开发一个新项目。这是到目前为止所做的:

  1. 技术设计。
  2. 模型类(数据类)。
  3. 项目中的所有接口(但还没有实现)。

接下来我要做的是实现从骨架(高级方法)到嵌套对象的方法。不过,我想在编写实现之前为每个方法创建一个单元测试。首先实现高级方法不会有任何问题,因为我将使用接口并使用 DI 仅在外部 Java 配置文件中绑定具体实现。

我要实现的第一个方法称为 lookForChanges(),它既接受又 return 无效。此方法由 Spring 的调度程序 (@Scheduled) 调用,它管理整个过程:它从 DB 检索数据,从 Web 服务检索数据,比较它们,如果有任何变化则它更新数据库并向客户端发送 JMS 消息。当然,它不会自己做所有这些事情,而是会调用相关的 类 和方法。

所以我遇到的第一个问题是如何为 void 方法创建单元测试。在所有教程中,经过测试的方法总是接受参数和 return 结果。我已经找到了 in this question 的答案。他说,即使没有结果可检查,至少可以确保被测试方法中的方法被调用并且参数顺序正确。

我有点喜欢这个答案,但问题是我正在使用 TDD,所以与问这个问题的人相比,我在实现测试方法之前编写测试,所以我没有还不知道它将使用哪些方法以及以什么顺序使用。我可以猜到,但只有在该方法已经实施后我才会确定这一点。

那么,如何在实现 void 骨架方法之前对其进行测试?

So the first problem I'd had is how to create a unit test to a void method.

void 方法意味着合作者。你验证那些。

例子。假设我们需要一个将 System.in 复制到 System.out 的任务。我们将如何为此编写自动化测试?

void copy() {
    // Does something clever with System.in and System.out
}

但是如果你稍微眯起眼睛,你会发现你的代码确实像

void copy() {
    InputStream in = System.in;
    PrintStream out = System.out;
    // Does something clever with `in` and `out`
}

如果我们对此执行和提取方法重构,那么我们最终可能会得到看起来像

的代码
void copy() {
    InputStream in = System.in;
    PrintStream out = System.out;
    copy(in, out);
}

void copy(InputStream in, PrintStream out) {
    // Does something clever with `in` and `out`
}

后者是我们可以测试的 API - 我们配置协作者,将它们传递给被测系统,然后验证更改。

目前我们没有测试 void copy(),但没关系,因为那里的代码是 "so simple that there are obviously no deficiencies"。

请注意,从测试的角度来看,以下设计之间没有太大区别

{
    Task task = new Task();
    task.copy(in, out);
}

{
    Task task = new Task(in, out);
    task.copy();
}

{
    Task task = Task.createTask();
    task.copy(in, out)
}

{
    Task task = Task.createTask(in, out);
    task.copy();
}

一种思考方式是:我们先不写API,我们先写test

// Arrange the test context to be in the correct initial state
// ???
// Verify that the test context arrived in final state consistent with the specification.

也就是说,在您开始考虑 API 之前,您首先需要弄清楚您将如何评估 结果。

相同的想法,不同的拼写:如果无法检测到函数调用的效果,那么您还不如发送一个空操作。如果空操作不符合您的要求,那么某处一定有可观察到的效果——您只需要确定该效果是直接观察到(检查 return 值),还是通过代理(检查对解决方案中其他元素的影响,或扮演该元素角色的测试替身)。

OK so now I can pass params to the method for testing it, but what can I test?

你测试它应该做什么。

试试这个思想实验 - 假设你和我结对,你提出了这个界面

interface Task {
    void lookForChanges();
}

然后,经过深思熟虑,我实现了这个:

class NoOpTask implements Task {
    @Override
    void lookForChanges() {}
}

您将如何证明我的实施不满足要求?

您在问题中写的是 "it updates the database and sends a JMS message to a client",因此有两个断言需要考虑 - 数据库是否已更新,是否发送了 JMS 消息?

整个事情看起来像这样

Given:
    A database with data `A`
    A webservice with data `B`
    A JMS client with no messages

When:
    The task is connected to this database, webservice, and JMS client
    and the task is run

Then:
    The database is updated with data `B`
    The JMS client has a message.

It looks like what you suggest is an end-to-end test.

确实像一个。但是,如果您对这些合作者使用测试替身,而不是实时系统,那么您的测试是 运行 在孤立且确定的 shell.

这可能是一个社交测试 - 测试不知道或不关心 when 子句中被测系统的实现细节。我没有声称 SUT 是一且恰好一 "unit".

I have to see the implementation of foo first. Am I wrong?

是的 - 您需要了解 foo 的规范,而不是实现。