单元测试 "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
) 编写单元测试。此方法:
- 有两个参数,
buzz
和isFoobaz
- 使用 class 属性
wsClient
进行 network/REST 调用
- 调用私有辅助方法,它不仅适用于外部文件系统,而且 "swallows" 检查异常;因此
readFileFromFileSystem
可以抛出 RuntimeExceptions
我们可以为此编写什么样的单元测试 "test the contract"?
验证输入(buzz
和 isFoobaz
)是显而易见的;合同应该定义每个有效的 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 等模拟框架创建 FileProvider
和 File
的模拟实现,并将模拟 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 和方法的交互。
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
) 编写单元测试。此方法:
- 有两个参数,
buzz
和isFoobaz
- 使用 class 属性
wsClient
进行 network/REST 调用 - 调用私有辅助方法,它不仅适用于外部文件系统,而且 "swallows" 检查异常;因此
readFileFromFileSystem
可以抛出RuntimeExceptions
我们可以为此编写什么样的单元测试 "test the contract"?
验证输入(buzz
和 isFoobaz
)是显而易见的;合同应该定义每个有效的 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 等模拟框架创建 FileProvider
和 File
的模拟实现,并将模拟 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 和方法的交互。