是否有可能在JUnit中覆盖预期的异常?

时间:2016-12-28 14:35:49

标签: java exception testing junit mockito

我最近发现了JUnit> 4.10允许使用@RuleExpectedException。由于我在复制代码方面不大,所以我尝试了以下方法。为了更好地理解,我将其从几个测试缩小到这两个。 MockitoJUnitRunner是故意的,虽然它没有在小缩放示例中使用。

的pom.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>

TestBase

 @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注释。但是,我希望有人可以帮助我理解为什么这不起作用?

2 个答案:

答案 0 :(得分:3)

它没有按预期工作的原因(没有双关语意)与TestRule如何使用JUnit有关。

有效的是,测试框架检查TestRule个实例的测试用例,然后依次调用每个实例上的TestRule.apply()方法。此方法采用Statement对象并返回Statement()。您的测试用例对象最初包含在一个Statement中,该Statement被赋予第一个TestRule,它返回一个包含原始Statement全新 Statement。因此,基本上,TestRule有机会适应原始TestCase,通常会添加新功能。一旦框架经历了所有TestRule实例,它就会调用Statement.evaluate()方法,就像它建立的任何“标准”测试用例一样,它没有附加任何TestRule。

这里的关键是框架和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有机会在测试用例完成后运行一些代码。这就是ExpectedException的工作原理(尽管它也有一个'catch Exception(e)'块)。在测试过程中,您可以调用规则实例上的方法,该方法在TestRule对象中构建状态,然后在调用finally块时使用该方法。因此,当您调用'exception.expect(IllegalArgumentException.class)`时,测试规则将匹配器存储在列表中,并基本上使用该匹配器和您可能已设置的任何其他匹配来捕获异常。当您重置测试用例中的实例字段时,原始实例中的所有状态仍然存在,因此测试仍然失败。

要执行您想要执行的操作,您需要一种重置ExpectedException实例的内部状态的方法。不幸的是,ExpectedException类上没有允许您删除已添加的期望的方法。它真的有可能增加期望。而且,说实话,这是有充分理由的 - 您的测试应该按逻辑分组,并且越接近测试用例,就会添加更精细的细节。 “重置”期望的行为是删除而不是添加细节的行为,因此表明您的测试在逻辑上不能很好地分组。如果测试套件的某些部分增加了一些期望而另一部分删除了部分/全部预期,则会产生可维护性困难。

如果要在此处使用ExpectedException,则有2个选项。第一种是将测试类或测试基类分成两部分。一个套件应该用于期望IllegalArgumentException的测试,而另一个套件应该用于那些没有或者具有某种替代异常的测试。第二个是接受44个测试中固有的重复,这些测试必须明确声明它们期望异常,而只有4个测试没有。

有可能您可以通过某种方式使用JUnit Theories来实现您的目标,尽管这在很大程度上取决于您的测试用例的工作方式。

答案 1 :(得分:0)

解决方案是覆盖设置方法,

你也可以手动完成:

@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");
        }
    }

请查看以下有趣链接以了解更多信息:

祝你好运:)