这里是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
)编写单元测试。这个方法:
buzz
和isFoobaz
wsClient
进行网络/ REST调用readFileFromFileSystem
可以抛出RuntimeExceptions
我们可以为此编写什么类型的单元测试"测试合同"?
验证输入(buzz
和isFoobaz
)是显而易见的;合同应该定义每个有效的值/状态,以及如果它们无效,应该发生什么样的例外/结果。
但除此之外,我并不确定"合同"这里甚至可以,这使得为它编写测试非常困难。所以我想这个问题确实应该是" 我如何确定单元测试的合同是什么,然后如何编写针对合同而不是实现的测试? "
但对于SO问题,这个标题太长了。
答案 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之类的模拟框架可用于FileProvider
和File
的创建模拟实现,并将模拟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参数的一部分,可能指向临时文件)会受到该方法的影响
如果要对方法的输出进行单元测试,则可能需要重构以确定应该执行的操作(文件需要更新)与实际执行操作。