单元测试包装器方法是否有意义

11
这个问题有点哲学性质。假设我有一个像这样的方法:
public List<String> getStuffByName(@NotNull String name) throws SomeException {
        return someDependency.createQuery().byName(name).list().stream()
                .map(execution -> execution.getProcessInstanceId())
                .collect(Collectors.toList());
}

基本上它所做的就是调用一个依赖方法,然后使用流API处理它。
严格来说,单元测试只测试一个隔离的单元。因此,我会模拟依赖项,因为我认为它们也已经被测试过了。
如果我继续下去,最终会得到一个仅由在其他地方测试过的内容组成的方法。
例如,我的JMockit测试将如下所示:
public void test_get_processes_for_bkey_2(@Mocked ExecutionQuery query,
                                              @Mocked List<String> processes,
                                              @Mocked List<Execution> executions,
                                              @Mocked Stream<Execution> e_stream,
                                              @Mocked Stream<String> pid_stream,
                                              @Mocked Stream<String> pdef_stream

    ) {
    new Expectations() {
        {
            someDependency.createQuery(); result = query;
            query.byName("somefilter"); result = query;
            query.list(); result = executions;
            executions.stream(); result = e_stream;
            e_stream.map((Function) any); result = fin_stream;
            fin_stream.collect((Collector) any); result = processes;
            processes.size(); result = 2;
        }
    };

    assertEquals(tested.getStuffByName("somefilter").size(), 2);
}

但是这个测试实际上告诉我什么呢?

在测试驱动开发中,我是否应该省略这些“包装器”方法的测试?

对于这个问题,无论是独立于Jmockit或其他框架的专业方法是什么?

4个回答

6
一个“专业的方法”来测试这个问题是 模拟任何东西。一个好的测试应该尽可能地真实,同时保持足够的速度和稳定性。 单元测试(在纯/严格意义上,其中单元通过模拟与其依赖项隔离)被高估了。这不仅是我的说法; 像Kent Beck(TDD的主要“创造者”)和Martin Fowler等作者也不喜欢“纯”单元测试(例如,参见Is TDD Dead?)。
模拟最好只在特殊情况下使用,在这些情况下,由于实际原因,您无法轻松编写测试。根据我的经验,集成测试(没有或最小化模拟)证明要好得多。

3

单元测试的主要思想是测试代码的“单元”,而不是整个基础架构或组件之间的交互。如果您编写了一个方法,例如:

public int sum(int a, int b){
  return a + b;
}

你仍在使用包装类(int/Integer+方法),但在这种情况下,您应编写单元测试来检查您的sum方法是否有效,不是吗?因为您可能会做出以下类似操作的错误:

public int sum(int a, int b){
  return a;
}

这绝对是一个 bug - 但它只是一个包装器!

好的,有些单元测试了解底层系统(白盒测试),有些单元测试不知道里面有什么(黑盒测试,没有任何假设)。所以,是否要测试某些东西取决于您自己 - 如果您认为您的代码是无 bug 的,仅仅因为“它只是一个包装器”,那么我们应该停止编写测试,因为一切都可以被视为一个包装器。


我认为OP所说的是对用户定义方法的包装器,而不是对语言本地操作的包装器。当人们使用“包装器”一词时,通常指的是前者。 - undefined
1
@MehdiCharife 是的,虽然我认为从概念上来说它仍然适用。最初的问题是关于测试一个给定参数“name”的方法,它会调用内部对象来获取一些映射值。作者是对的:大多数方法都在其他地方进行了测试。问题是:这个方法getStuffByName 实际上 通过名称获取东西有多重要?如果代码忘记添加“.byName(...)”方法会怎么样?等等。最后这是抽象的叠加,但它仍然是一个包装器,你必须测试代码的逻辑。 - undefined

3

您需要测试所有公共方法,包括包含其他方法逻辑的方法。此方法的单元测试通过告诉您以下几点重要信息:

  • getStuffByName 方法存在;
  • getStuffByName 的签名是您期望的;
  • getStuffByName 的逻辑已被单元测试中的检查确定。

如果有人决定使用循环而不是流重新实现您的getStuffByName方法,因为他们想将您的代码回退到早期版本的Java,则通过单元测试能够告诉他们其转换工作是否正确。


1
该方法的合同是执行具有给定名称的查询,并返回查询返回的Execution实例的进程实例ID。无论您的方法使用流、for循环或任何其他方式都是不相关的。
因此,我只会存根查询并使其返回2或3个Execution实例的真实列表。基于查询返回的列表,我将检查返回的List是否包含适当的2或3个ID,并且顺序正确。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接