单元测试 "test the contract" 如何在 returns 无效的方法上进行?

How can a unit test "test the contract" on a method that returns void?

Java 8 这里但这是一个一般的单元测试问题(很可能)language-agnostic.

编写 JUnit 测试的语法很简单,但要决定 编写什么测试如何 测试main/production 代码是我发现最大的挑战。在阅读单元测试最佳实践时,我一遍又一遍地听到同样的话:

Test the contract

相信 单元测试不应该是脆弱的,并且如果方法的实现发生变化也不一定会中断。该方法应该定义输入合同 -> results/outcomes 并且测试应该旨在验证合同是否得到遵守。我觉得。

假设我有以下方法:

public void doFizzOnBuzz(Buzz buzz, boolean isFoobaz) {
    // wsClient is a REST client for a microservice
    Widget widget = wsClient.getWidgetByBuzzId(buzz.getId());

    if(widget.needsFile()) {
        File file = readFileFromFileSystem(buzz.getFile());

        if(isFoobaz) {
            // Do something with the file (doesn't matter what)
        }
    }

    return;
}

private File readFileFromFileSystem(String filename) {
    // Private helper method; implementation doesn't matter here EXCEPT...
    // Any checked exceptions that Java might throw (as a result of working)
    // with the file system are wrapped in a RuntimeException (hence are now
    // unchecked.

    // Reads a file from the file system based on the filename/URI you specify
}

所以在这里,我们有一个方法,我们希望为 (doFizzOnBuzz) 编写单元测试。此方法:

我们可以为此编写什么样的单元测试 "test the contract"?

验证输入(buzzisFoobaz)是显而易见的;合同应该定义每个有效的 values/states 是什么,以及如果它们无效时应该发生什么 exceptions/results。

但除此之外,我真的不确定这里的 "contract" 是什么,这使得为它编写测试非常困难。所以我想这个问题真的应该类似于“我如何确定单元测试的契约是什么,然后你如何编写针对契约而非实现的测试?

但是这个标题对于 SO 问题来说太长了。

问题是你的合同方法没有说明你可以从外面观察到什么效果。它基本上是一个 BiConsumer,所以除了确保有无异常之外,没有太多的单元测试可能。

您可以做的测试是确保调用(模拟的)REST 服务,或者文件(Buzz 参数的一部分,可能指向临时文件)将受到下面方法的影响一些条件。

如果您想对方法的输出进行单元测试,您可能需要重构以将确定应该做什么(文件需要更新)与实际执行分开。

您使用方法 doFizzOnBuzz(Buzz buzz, boolean isFoobaz)private File readFileFromFileSystem(String filename) 的代码不易测试,因为第一个方法将尝试读取文件,而这不是您希望在测试中执行的操作。

在这里,doFizzOnBuzz 需要一些东西来提供一个文件供其使用。这个 FileProvider(我会这样称呼它)可以是一个接口,类似于:

public interface FileProvider {
  File getFile(String filename);
}

当 运行 在生产中时,使用实际从磁盘读取文件的实现,但是当单元测试 doFizzOnBuzz 时,可以使用 FileProvider 的模拟实现。这 return 是一个模拟 File

要记住的关键点是,在测试 doFizzOnBuzz 时,我们 不是 测试任何提供文件或其他任何东西。我们假设要正常工作。这些其他代码有它们自己的单元测试。

可以使用 Mockito 等模拟框架创建 FileProviderFile 的模拟实现,并将模拟 FileProvider 注入 [=51] =] 正在测试中,可能使用 setter:

public void setFileProvider(FileProvider f) {
  this.fileProvider = f;
}

此外,我不知道 wsClient 是什么,但我知道它有一个 getWidgetByBuzzId() 方法。这个 class 也可以是一个接口,出于测试目的,接口将被模拟,并且 return 是一个模拟 Widget,类似于上面的 FileProvider。

使用 mockito,您不仅可以设置接口的模拟实现,还可以定义在该接口上调用方法时 returned 的值:例如

//setup mock FileProvider
FileProvider fp = Mockito.mock(FileProvider.class);

//Setup mock File for FileProvider to return
File mockFile = Mockito.mock(File.class);
Mockito.when(mockFile.getName()).thenReturn("mockfilename");
//other methods...

//Make mock FileProvider return mock File
Mockito.when(fp.getFile("filename")).thenReturn(mockFile);

ClassUnderTest test = new ClassUnderTest();
test.setFileProvider(fp); //inject mock file provider

//Also set up mocks for Buzz,, Widget, and anything else

//run test
test.doFizzOnBuzz(...)

//verify that FileProvider.getFile() was actually called:
Mockito.verify(fp).getFile("filenane"); 

如果未使用参数 'filename'

调用 getFile(),则上述测试将失败

结论 如果您无法直接观察方法的结果,例如它是无效的,您可以使用 Mocking 来验证它与其他 classes 和方法的交互。