一个单位如何测试和测试合同"在返回void的方法?

时间:2016-03-31 08:55:00

标签: java unit-testing design-by-contract

这里是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调用
  • 调用私有帮助器方法,该方法不仅可以与外部文件系统一起使用,而且可以使用#34;吞下"检查异常;因此readFileFromFileSystem可以抛出RuntimeExceptions

我们可以为此编写什么类型的单元测试"测试合同"?

验证输入(buzzisFoobaz)是显而易见的;合同应该定义每个有效的值/状态,以及如果它们无效,应该发生什么样的例外/结果。

但除此之外,我并不确定"合同"这里甚至可以,这使得为它编写测试非常困难。所以我想这个问题确实应该是" 我如何确定单元测试的合同是什么,然后如何编写针对合同而不是实现的测试? "

但对于SO问题,这个标题太长了。

2 个答案:

答案 0 :(得分:2)

使用方法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的创建模拟实现,并将模拟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(),则上述测试失败

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

答案 1 :(得分:1)

问题是你的合同方法没有告诉你从外面可以观察到什么影响。它基本上是一个BiConsumer,所以确保有一个例外与否,没有太多的单元测试可能。

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

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