繁体   English   中英

单元测试如何使用返回无效的方法来“测试合同”?

[英]How can a unit test “test the contract” on a method that returns void?

Java 8在这里,但这是一个一般的单元测试问题,(可能)与语言无关。

编写JUnit测试的语法很容易,但是决定要编写哪些测试以及如何测试主/生产代码是我发现最大的挑战。 在阅读有关单元测试的最佳实践时,我一遍又一遍地听到相同的事情:

测试合同

相信这里的想法是,如果方法的实现发生变化,则单元测试不应太脆弱,也不必一定会破坏。 该方法应定义输入合同->结果/结果,并且测试应旨在验证合同是否得到遵守。 我认为。

假设我有以下方法:

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 )编写单元测试。 该方法:

  • 有两个参数, buzzisFoobaz
  • 使用类属性wsClient进行网络/ REST调用
  • 调用一个私有帮助器方法,该方法不仅可以与外部文件系统一起使用,而且可以“吞下”已检查的异常; 因此readFileFromFileSystem可能会抛出RuntimeExceptions

我们可以为此编写“测试合同”的哪种单元测试?

验证输入( buzzisFoobaz )是显而易见的。 合同应定义每个值的有效值/状态,以及无效的异常/结果。

但是除此之外,我还不确定这到底是什么“合同”,这使得编写测试非常困难。 因此,我想这个问题确实应该是这样的:“ 如何确定单元测试的合同,然后如何编写针对合同而不是实现的测试?

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

使用doFizzOnBuzz(Buzz buzz, boolean isFoobaz)private File readFileFromFileSystem(String filename)方法的代码不容易测试,因为第一种方法将尝试读取文件,而这不是您要在测试中要做的事情。

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

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

当在生产运行,实现真正从磁盘中读取文件时使用,但在单元测试doFizzOnBuzz模拟实现的FileProvider可以用来代替。 这将返回一个模拟File

要记住的关键点是,在测试doFizzOnBuzz ,我们不会测试提供文件的文件或其他文件。 我们以为可以正常工作。 这些其他代码具有自己的单元测试。

可以使用诸如Mockito之类的模拟框架来创建FileProviderFile模拟实现,并可以使用setter将模拟FileProvider注入FileProvider类中:

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

另外,我不知道wsClient是什么,我确实知道它具有getWidgetByBuzzId()方法。 此类也可以是接口,并且出于测试目的,将对接口进行模拟,并返回模拟Widget ,类似于上面的FileProvider。

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

//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(),则上述测试将失败

结论如果不能直接观察方法的结果(例如它无效),则可以使用模拟来验证其与其他类和方法的交互。

问题在于您的合同方法无法说明您可以从外部观察到什么效果。 它基本上是一个BiConsumer,因此从确保是否有异常的角度出发,没有太多的单元测试可能。

您可以做的测试是确保调用(模拟的)REST服务,或者在某些情况下该方法会影响File(Buzz参数的一部分,可能指向一个临时文件)。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM