我最近发现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>


测试库

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

要执行您想做的事情,您需要一种重置ExpectedException实例的内部状态的方法。不幸的是,ExpectedException类上没有任何方法可以删除已添加的期望。确实只有增加期望。而且,说实话,这是有充分理由的-您的测试应按逻辑分组,并在接近测试用例时添加更细粒度的详细信息。 “重置”期望的行为是删除而不是添加细节的行为,因此建议您的测试在逻辑上不够合理地分组。如果测试套件的某些部分增加了一些期望,而另一部分则消除了部分/全部,则会带来可维护性的困难。

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

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

09-25 22:17