简体   繁体   English

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

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

Java 8 here but this is a general unit testing question that (is likely) language-agnostic. Java 8在这里,但这是一个一般的单元测试问题,(可能)与语言无关。

The syntax of writing a JUnit test is easy, but deciding on what tests to write and how to test main/production code is what I find to be the biggest challenge. 编写JUnit测试的语法很容易,但是决定要编写哪些测试以及如何测试主/生产代码是我发现最大的挑战。 In reading up on unit testing best practices, I keep hearing the same thing over and over again: 在阅读有关单元测试的最佳实践时,我一遍又一遍地听到相同的事情:

Test the contract 测试合同

I believe the idea there is that unit tests should not be brittle and should not necessarily break if the method's implementation changes. 相信这里的想法是,如果方法的实现发生变化,则单元测试不应太脆弱,也不必一定会破坏。 That the method should define a contract of inputs -> results/outcomes and that the tests should aim to verify that contract is being honored. 该方法应定义输入合同->结果/结果,并且测试应旨在验证合同是否得到遵守。 I think. 我认为。

Let's say I have the following method: 假设我有以下方法:

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
}

So here, we have a method we wish to write unit tests for ( doFizzOnBuzz ). 因此,在这里,我们有一种方法希望为( doFizzOnBuzz )编写单元测试。 This method: 该方法:

  • Has two parameters, buzz and isFoobaz 有两个参数, buzzisFoobaz
  • Uses a class property wsClient to make a network/REST call 使用类属性wsClient进行网络/ REST调用
  • Calls a private helper method that not only works with the external file system, but that "swallows" checked exceptions; 调用一个私有帮助器方法,该方法不仅可以与外部文件系统一起使用,而且可以“吞下”已检查的异常; hence readFileFromFileSystem could throw RuntimeExceptions 因此readFileFromFileSystem可能会抛出RuntimeExceptions

What kinds of unit tests can we write for this that "test the contract"? 我们可以为此编写“测试合同”的哪种单元测试?

Validating inputs ( buzz and isFoobaz ) are obvious ones; 验证输入( buzzisFoobaz )是显而易见的。 the contract should define what valid values/states for each of those are, and what exceptions/results should occur if they are invalid. 合同应定义每个值的有效值/状态,以及无效的异常/结果。

But beyond that, I'm not really sure what the "contract" here would even be, which makes writing tests for it very difficult. 但是除此之外,我还不确定这到底是什么“合同”,这使得编写测试非常困难。 So I guess this question really should be something like " How do I determine what the contract is for a unit test, and then how do you write tests that target the contract and not the implementation? " 因此,我想这个问题确实应该是这样的:“ 如何确定单元测试的合同,然后如何编写针对合同而不是实现的测试?

But that title would be too long for a SO question. 但是这个标题对于SO问题来说太长了。

Your code with the methods doFizzOnBuzz(Buzz buzz, boolean isFoobaz) and private File readFileFromFileSystem(String filename) is not easily testable, because the first method will try and read a file, and that's not something you want to do in test. 使用doFizzOnBuzz(Buzz buzz, boolean isFoobaz)private File readFileFromFileSystem(String filename)方法的代码不容易测试,因为第一种方法将尝试读取文件,而这不是您要在测试中要做的事情。

Here, doFizzOnBuzz needs something to provide a File for it to work with. 在这里, doFizzOnBuzz需要一些东西来提供文件供其使用。 This FileProvider (as I'll call it) could be an interface, something like: 这个FileProvider (我称它为)可能是一个接口,类似于:

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

When running in production, an implementation to actually read the file from disk is used, but when unit testing doFizzOnBuzz a mock implementation of FileProvider could be used instead. 当在生产运行,实现真正从磁盘中读取文件时使用,但在单元测试doFizzOnBuzz模拟实现的FileProvider可以用来代替。 This returns a mock File . 这将返回一个模拟File

The key point to remember is that when testing doFizzOnBuzz , we are not testing whatever provides the file, or anything else. 要记住的关键点是,在测试doFizzOnBuzz ,我们不会测试提供文件的文件或其他文件。 We assume that to working correctly. 我们以为可以正常工作。 These other bits of code have their own unit tests. 这些其他代码具有自己的单元测试。

A mocking framework such as Mockito can be used a create mock implementations of FileProvider and File , and to inject the mock FileProvider into the class under test, probably using a setter: 可以使用诸如Mockito之类的模拟框架来创建FileProviderFile模拟实现,并可以使用setter将模拟FileProvider注入FileProvider类中:

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

Also, I don't know what a wsClient is, bit I do know it has a getWidgetByBuzzId() method. 另外,我不知道wsClient是什么,我确实知道它具有getWidgetByBuzzId()方法。 This class too could be an interface, and for testing purposes the interface would be mocked, and return a mock Widget , similar to the FileProvider above. 此类也可以是接口,并且出于测试目的,将对接口进行模拟,并返回模拟Widget ,类似于上面的FileProvider。

With mockito, not only can you set up mock implementations of interfaces, you can also define what values are returned when methods are called on that interface: eg 使用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"); 

The above test fails if getFile() was not called with the parameter 'filename' 如果未使用参数'filename'调用getFile(),则上述测试将失败

Conclusion If you cannot directly observe the results of a method, eg it is void, you can use Mocking to verify its interaction with other classes and methods. 结论如果不能直接观察方法的结果(例如它无效),则可以使用模拟来验证其与其他类和方法的交互。

The problem is that your contract method does not tell what effect you can observe from the outside. 问题在于您的合同方法无法说明您可以从外部观察到什么效果。 It is basically a BiConsumer, so appart from ensuring there is an exception or not, there is not much unit testing possible. 它基本上是一个BiConsumer,因此从确保是否有异常的角度出发,没有太多的单元测试可能。

The test you could do is to ensure that the (Mocked) REST service is called, or that the File (part of the Buzz parameter, which might be pointing to a temporary file) will be impacted by the method under some conditions. 您可以做的测试是确保调用(模拟的)REST服务,或者在某些情况下该方法会影响File(Buzz参数的一部分,可能指向一个临时文件)。

If you want to unit test the output of the method, you may need to refactor to separate the determination of what should be done (file needs update) from actually doing it. 如果要对方法的输出进行单元测试,则可能需要重构以将确定应该执行的操作(文件需要更新)与实际执行的操作分开。

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

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