我最近发现 JUnit> 4.10 允许使用@Rule
和ExpectedException
。由于我不擅长复制代码,因此我尝试了以下内容。为了更好地理解,我将其从几个测试缩减到这两个测试。MockitoJUnitRunner
是有意为之的,尽管它未在小比例示例中使用。
绒球.xml
<dependencies>
<!-- Test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
</dependencies>
测试库
@RunWith(MockitoJUnitRunner.class)
public class TestBase {
/** JUnit > 4.10 allows expected exception handling like this */
@Rule
public ExpectedException exception = ExpectedException.none();
@Before
public void setup() {
this.expectBadParam();
}
protected void expectBadParam() {
this.exception.expect(NullPointerException.class);
}
}
问题是以下测试没有像我预期的那样工作。我正在尝试的是默认情况下期望异常类型,并在某些情况下运行正常的 JUnit 测试。设置后,我无法重置预期的异常。
public class ExpectedExceptionTest extends TestBase {
@Test
public void error() {
throw new NullPointerException();
}
@Test
public void success() {
this.exception = ExpectedException.none();
// this should be a success
}
}
我已经通过在我期望异常的每个方法中复制 expectBadParam 方法以及覆盖测试类中的@Before
注释找到了不同的解决方案。但是我希望有人可以帮助我理解为什么这不起作用?
它没有按预期工作的原因(没有双关语)与TestRule
如何与 JUnit 一起工作有关。
实际上,测试框架检查测试用例中是否有TestRule
实例,然后依次在每个实例上调用TestRule.apply()
方法。此方法获取一个Statement
对象并返回一个Statement()
。您的测试用例对象最初包装在一个语句中,该语句提供给第一个 TestRule,该语句返回一个包装原始Statement
的全新Statement
。因此,基本上,TestRule有机会适应原始TestCase,通常会添加新功能。一旦框架遍历了所有的TestRule实例,它就会调用Statement.evaluate()
方法,就像它对它构建的任何"标准"测试用例所做的那样,它没有任何TestRules附加到它。
这里的关键是框架和TestRule
实例之间的所有交互都发生在测试用例构建时。构建测试用例后,测试框架将不再查询或直接与包含规则的字段进行交互。之后,它们的主要目的是让测试与规则中包含的可变状态进行交互。因此,如果您像在测试用例中那样更改实例字段success()
则绝对不会影响规则的结果,因为期望IllegalArgumentException
的规则已经应用于测试用例。
TestRule
实现有一个"典型"形状。他们看起来像这样...
public void apply(Statement base, Description description) {
return new Statement() {
public void evaluate( ) {
// some initialisation
try {
base.evaluate();
} finally {
// some tidy up here
}
}
}
}
在这里,TestRule 有机会在测试用例完成后运行一些代码。这就是 ExpectException 的工作方式(尽管它也有一个"catch Exception(e)"块)。在测试过程中,您可以在规则实例上调用方法,该方法在 TestRule 对象中构建状态,然后在调用 finally 块时使用。因此,当您调用"exception.expect(IllegalArgumentException.class)"时,测试规则将匹配器存储在列表中,并且基本上使用该匹配器和您可能已设置的任何其他匹配器匹配捕获的异常。当您重置测试用例中的实例字段时,原始实例中的所有状态仍然存在,因此测试仍然失败。
要执行您想要执行的操作,您需要一种重置ExpectedException
实例内部状态的方法。遗憾的是,ExpectedException
类上没有任何方法允许您删除已添加的期望。只有真正有可能增加期望。而且,老实说,这是有充分理由的 - 您的测试应该在逻辑上分组,越接近测试用例,就会添加更细粒度的细节。"重置"期望的行为是删除而不是添加细节的行为,因此表明您的测试在逻辑上分组不够好。如果测试套件的某些部分增加了一些期望,而另一部分删除了部分/全部期望,则会产生可维护性困难。
如果要在此处使用预期异常,您有 2 个选项。第一种方法是将测试类或测试基类一分为二。一个套件应该用于预期 IllegalArgumentException 的测试,另一个套件用于那些没有或具有他们期望的某种替代异常的测试。第二种是接受 44 个测试固有的重复,这些测试必须明确声明它们期望异常,只有 4 个测试不需要。
您可能以某种方式使用JUnitTheories
实现您想要的,尽管这在很大程度上取决于您的测试用例的工作方式。
解决方案是覆盖设置方法,
您也可以手动执行此操作:
@Test
public void success() {
try{
... all your code
} catch (Exception e){
// check your nested clauses
if(e.getCause() instanceof ExpectedException){
// pass
} else {
Assert.fail("unexpected exception");
}
}
请在下面找到有趣的链接以了解更多信息:
test-custom-exceptions-w-junits
显示如何使用 Hamcrest 测试自定义异常的代码 匹配器
祝你好运:)