Junit @Before/@After被调用的顺序是什么?

146
我有一个集成测试套件。我有一个IntegrationTestBase类供所有测试扩展使用。该基类具有一个@Beforepublic void setUp())和@Afterpublic void tearDown())方法以建立API和DB连接。我一直在做的就是在每个测试用例中覆盖这两个方法,并调用super.setUp()super.tearDown()。但是,如果有人忘记调用超级或将它们放在错误的位置并抛出异常,他们会忘记在finally中调用super等,这可能会导致问题。
我想做的是使基类上的setUptearDown方法为final,然后只需添加我们自己的注释@Before@After方法即可。经过一些初步测试,它似乎总是按此顺序调用:
Base @Before
Test @Before
Test
Test @After
Base @After

但我有点担心顺序不能保证,可能会引起问题。我找了找,没有看到有关这个问题的任何内容。有人知道我是否可以这样做而不会有任何问题吗?

代码:

public class IntegrationTestBase {

    @Before
    public final void setUp() { *always called 1st?* }

    @After
    public final void tearDown() { *always called last?* }
}


public class MyTest extends IntegrationTestBase {

    @Before
    public final void before() { *always called 2nd?* }

    @Test
    public void test() { *always called 3rd?* }

    @After
    public final void after() { *always called 4th?* }
}
6个回答

152

是的,这种行为是有保证的:

@Before

超类中的@Before方法会在当前类的@Before方法之前运行,除非在当前类中被覆盖。没有其他排序定义。

@After

超类中声明的@After方法将在当前类的@After方法之后运行,除非在当前类中被覆盖。


21
要明确的是,所有@Before方法的执行顺序不能保证。如果有10个@Before方法,它们中的每一个都可以在任何顺序下执行;就在其他方法之前执行。 - Swati
8
你能否用自己的话解释一下,而不是引用有些模糊的文档?@Before@After方法是在每个其他类方法之前运行(每个方法运行一次),还是只在整个类方法套件之前和之后运行一次(每个类只运行一次)? - B T
8
请注意John Q Citizen所提到的重要细节: “如果每个被标记为@Before的方法在类层次结构中都具有唯一名称,则此规则才适用。”请务必记住! - Bruno Bossola
我在junit-4.12中遇到了一个名称冲突的问题,因为我在一个类的@Before(d)方法和其父类的另一个方法中使用了相同的方法名。 - Stephane
这个规则是否也适用于ConcordionRunner的@BeforeExample方法? - Adrian Pronk
根据FixtureInstance.java中的invokeMethods,似乎在调用@BeforeExample@AfterExample(以及所有其他Concordion注释的方法)时,父类方法将先于子类方法被调用。 - Adrian Pronk

58

有一个可能会让我困扰的问题:

我喜欢在每个测试类中最多只有一个@Before方法,因为不能保证按照类内定义的@Before方法的顺序运行。通常,我会将这样的方法命名为setUpTest()

但是,尽管@Before被记录为超类的@Before方法将在当前类的@Before方法之前运行。没有其他排序定义。,但仅当标有@Before的每个方法在类层次结构中具有唯一名称时,此规则才适用。

例如,我曾经遇到过以下情况:

public class AbstractFooTest {
  @Before
  public void setUpTest() { 
     ... 
  }
}

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() { 
    ...
  }
}

我原以为AbstractFooTest.setUpTest()会在FooTest.setUpTest()之前运行,但只有FooTest.setupTest()被执行了。 AbstractFooTest.setUpTest()根本没有被调用。

代码必须按以下方式修改才能正常工作:

public void FooTest extends AbstractFooTest {
  @Before
  public void setUpTest() {
    super.setUpTest();
    ...
  }
}

1
为什么不直接更改基类中@Before方法的名称?这样可以避免在所有子类中调用super...无论如何,你发现了相同名称问题,干得好。 - Lawrence Tierney
31
为了让事情更加安全,我有一个建议:为了避免名称冲突,你可以在基类中将 @Before/@After 方法声明为 final,这样如果你(无意间)尝试在子类中重写它们,编译器会报错。 - Stefan Winkler
6
同名的父类方法未被执行,这似乎并不像是JUnit的行为,而更像是面向对象编程中基本覆盖(overriding)的工作原理。在运行时,父类方法基本上不存在,子类方法代替了它的所有功能。这就是Java的工作方式。 - Brandon
1
另一个需要注意的是,父类必须是公共的,否则如果子类也有@Before方法,则其@Before标记的方法将被忽略。 - rusins
这是现有的行为文档。 “超类的方法将在当前类的方法之前运行, 除非它们在当前类中被覆盖。没有其他排序定义。” 这允许使用@Before混合接口,但如果给出了覆盖,则也可以防止它们执行。 - iwat0qs

26

根据 @Before@After 的文档,我认为正确的结论是给方法命名独特的名称。在我的测试中,我使用以下模式:

public abstract class AbstractBaseTest {

  @Before
  public final void baseSetUp() { // or any other meaningful name
    System.out.println("AbstractBaseTest.setUp");
  }

  @After
  public final void baseTearDown() { // or any other meaningful name
    System.out.println("AbstractBaseTest.tearDown");
  }
}

public class Test extends AbstractBaseTest {

  @Before
  public void setUp() {
    System.out.println("Test.setUp");
  }

  @After
  public void tearDown() {
    System.out.println("Test.tearDown");
  }

  @Test
  public void test1() throws Exception {
    System.out.println("test1");
  }

  @Test
  public void test2() throws Exception {
    System.out.println("test2");
  }
}

作为结果给出

AbstractBaseTest.setUp
Test.setUp
test1
Test.tearDown
AbstractBaseTest.tearDown
AbstractBaseTest.setUp
Test.setUp
test2
Test.tearDown
AbstractBaseTest.tearDown

这种方法的优点是:AbstractBaseTest类的用户不会意外地覆盖setUp/tearDown方法。如果他们想要覆盖,就需要知道确切的名称并进行操作。

这种方法的(轻微)缺点是:用户无法看到setUp/tearDown之前或之后发生了什么。他们需要知道这些事情是由抽象类提供的。但我认为这就是使用抽象类的原因。


2
不错的例子 - 如果您有两个@Test方法,那么它将更具说明性,因此可以看到setUp和tearDown包装每个测试方法。 - Mark
我认为这是对原帖最好回答的基础,但你应该单独填写你的答案。你能否扩展你的示例以涵盖其他人提出的替代方案,并解释为什么你的建议更优越? - wachr

2

这不是对标签问题的回答,但它是对问题主体中提到的问题的回答。请考虑使用@org.junit.Rule而不是使用@Before或@After,因为它可以给你更大的灵活性。如果您正在管理连接,则最感兴趣的规则是ExternalResource(自4.7起)。此外,如果您想保证规则的执行顺序,请使用RuleChain(自4.10起)。我相信所有这些规则在提问时都是可用的。下面的代码示例是从ExternalResource的Java文档中复制的。

 public static class UsesExternalResource {
  Server myServer= new Server();

  @Rule
  public ExternalResource resource= new ExternalResource() {
      @Override
      protected void before() throws Throwable {
          myServer.connect();
         };

      @Override
      protected void after() {
          myServer.disconnect();
         };
     };

  @Test
  public void testFoo() {
      new Client().run(myServer);
     }
 }

2

如果你反过来看,你可以声明你的基类为抽象类,并让子类声明setUp和tearDown方法(没有注释),这些方法将会在基类的带注释setUp和tearDown方法中被调用。


1
不是一个坏主意,但我不想在不需要自己的setUp/tearDown测试上强制执行合同。 - Joel

2
您可以使用@BeforeClass注释来确保始终首先调用setup()。同样,您可以使用@AfterClass注释来确保最后调用tearDown()
这通常不被推荐,但它是支持的
这不完全是您想要的,但实质上它会在测试运行期间保持您的数据库连接打开状态,然后在最后一次关闭它。

2
实际上,如果您要这样做,我建议创建一个名为setupDB()closeDB()的方法,并用@BeforeClass@AfterClass标记它们,并将您的before/after方法替换为setup()tearDown() - Swati
@BeforeClass@AfterClass注释的方法需要是静态的。但是,如果我们想在这些方法中使用实例变量呢? - Pratik Singhal
使用Powermock时,使用@BeforeClass时需要注意:它仅适用于第一次测试运行。请参阅此问题:https://github.com/powermock/powermock/issues/398 - Dagmar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接