Thứ tự nào là Junit @ Before / @ Sau khi được gọi?


133

Tôi có một bộ kiểm tra tích hợp. Tôi có một IntegrationTestBaselớp cho tất cả các bài kiểm tra của tôi để mở rộng. Lớp cơ sở này có một phương thức @Before( public void setUp()) và @After( public void tearDown()) để thiết lập các kết nối API và DB. Những gì tôi đã làm chỉ là ghi đè hai phương thức đó trong mỗi testcase và gọi super.setUp()super.tearDown(). Tuy nhiên, điều này có thể gây ra vấn đề nếu ai đó quên gọi siêu hoặc đặt sai vị trí và một ngoại lệ được ném và họ quên gọi siêu cuối cùng hoặc một cái gì đó.

Những gì tôi muốn làm là tạo setUptearDowncác phương thức trên lớp cơ sở finalvà sau đó chỉ cần thêm các phương thức @Beforevà chú thích của riêng chúng ta @After. Thực hiện một số thử nghiệm ban đầu có vẻ như luôn gọi theo thứ tự này:

Base @Before
Test @Before
Test
Test @After
Base @After

nhưng tôi chỉ lo ngại một chút rằng đơn hàng không được đảm bảo và nó có thể gây ra vấn đề. Tôi nhìn xung quanh và không thấy gì về chủ đề này. Có ai biết nếu tôi có thể làm điều đó và không có bất kỳ vấn đề?

Mã số:

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?* }
}

1
MyTestthiếu một extends?
aioobe

@aioobe: không còn nữa :)
Joel

Câu trả lời:


135

Có, hành vi này được đảm bảo:

@Before:

Các @Beforephương thức của siêu lớp sẽ được chạy trước các phương thức của lớp hiện tại, trừ khi chúng bị ghi đè trong lớp hiện tại. Không có thứ tự khác được xác định.

@After:

Các @Afterphương thức được khai báo trong các siêu lớp sẽ được chạy sau các phương thức của lớp hiện tại, trừ khi chúng bị ghi đè trong lớp hiện tại.


15
Để rõ ràng, thứ tự thực hiện của tất cả các @Beforephương thức không được đảm bảo. Nếu có 10 @Beforephương thức, mỗi phương thức có thể được thực hiện theo bất kỳ thứ tự nào; ngay trước bất kỳ phương pháp nào khác.
Swati

5
Vì vậy, thay vì trích dẫn các tài liệu hơi mơ hồ, bạn có thể vui lòng giải thích nó bằng lời nói của bạn? Là @Before@Aftercác phương thức chạy trước mọi phương thức lớp khác (một lần cho mỗi phương thức), hay chỉ trước và sau toàn bộ bộ phương thức lớp (một lần cho mỗi lớp)?
BT

5
Xem phần nắm bắt quan trọng được nêu bởi John Q Citizen: "điều này chỉ áp dụng nếu mỗi phương thức được đánh dấu bằng @B Before có một tên duy nhất trong hệ thống phân cấp lớp" Rất quan trọng cần nhớ!
Bruno Bossola

Tôi đã có một xung đột tên khi sử dụng cùng tên phương thức trên phương thức @B Before (d) trong một lớp và một phương thức khác trong siêu lớp của nó, trên junit-4.12.
Stephane

Có phải quy tắc này cũng áp dụng cho các phương thức @B BeforeExample của ConcordionRunner?
Adrian Pronk

51

Một bí mật tiềm năng đã cắn tôi trước đây:

Tôi muốn có nhiều nhất một @Beforephương thức trong mỗi lớp kiểm tra, bởi vì thứ tự chạy các @Beforephương thức được định nghĩa trong một lớp không được đảm bảo. Thông thường, tôi sẽ gọi một phương pháp như vậy setUpTest().

Nhưng, mặc dù @Befoređược ghi lại dưới dạng The @Before methods of superclasses will be run before those of the current class. No other ordering is defined., điều này chỉ áp dụng nếu mỗi phương thức được đánh dấu @Beforecó một tên duy nhất trong hệ thống phân cấp lớp.

Ví dụ, tôi đã có những điều sau đây:

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

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

Tôi dự kiến ​​sẽ AbstractFooTest.setUpTest()chạy trước FooTest.setUpTest(), nhưng chỉ FooTest.setupTest()được thực hiện. AbstractFooTest.setUpTest()đã không được gọi ở tất cả.

Mã phải được sửa đổi như sau để làm việc:

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

Tại sao không chỉ thay đổi tên của phương thức @B Before trong lớp cơ sở? Điều này sẽ giúp bạn không phải gọi siêu nhân trong tất cả trẻ em ... dù sao thì cũng rất tốt với vấn đề cùng tên
Lawrence Tierney

24
Chỉ là một nhận xét để làm cho mọi thứ an toàn hơn: Để tránh xung đột tên, bạn có thể tạo @Before/ @Afterphương thức trong lớp cơ sở final, vì vậy trình biên dịch sẽ phàn nàn nếu bạn (vô tình) cố gắng ghi đè chúng trong lớp con.
Stefan Winkler

4
Phương thức cha mẹ cùng tên không được chạy không có vẻ giống như một hành vi JUnit. Nghe có vẻ như cách hoạt động quá sức cơ bản trong OOP. Phương thức cha về cơ bản không tồn tại trong thời gian chạy. Đứa trẻ thay thế nó cho tất cả ý định và mục đích. Đó là cách Java hoạt động.
Brandon

1
Một điều đáng chú ý khác là các lớp cha phải được công khai, nếu không các @Beforephương thức được đánh dấu của chúng sẽ bị bỏ qua nếu một lớp con cũng có một @Beforephương thức.
rusins

21

Tôi nghĩ dựa trên tài liệu của @Before@Afterkết luận đúng là đưa ra các phương thức tên duy nhất. Tôi sử dụng mẫu sau trong các thử nghiệm của mình:

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

cho kết quả

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

Ưu điểm của phương pháp này: Người dùng của lớp AbstractBaseTest không thể ghi đè các phương thức setUp / tornDown một cách tình cờ. Nếu họ muốn, họ cần biết tên chính xác và có thể làm điều đó.

(Nhỏ) nhược điểm của phương pháp này: Người dùng không thể thấy rằng có những điều xảy ra trước hoặc sau setUp / tornDown của họ. Họ cần biết rằng những điều này được cung cấp bởi lớp trừu tượng. Nhưng tôi cho rằng đó là lý do tại sao họ sử dụng lớp trừu tượng


2
ví dụ hay - sẽ còn minh họa hơn nữa nếu bạn có hai phương thức @Test, vì vậy có thể thấy rằng setUp và tornDown bao bọc từng phương thức thử nghiệm.
Đánh dấu

Tôi nghĩ rằng đây là cơ sở cho câu trả lời tốt nhất cho OP, nhưng bạn nên điền vào câu trả lời của mình cho độc lập. Bạn có thể nêu ví dụ của bạn để bao gồm các lựa chọn thay thế mà những người khác đã đề xuất, và giải thích lý do tại sao đề xuất của bạn là tốt hơn?
wachr

2

Nếu bạn xoay chuyển mọi thứ, bạn có thể khai báo trừu tượng lớp cơ sở của mình và yêu cầu con cháu khai báo các phương thức setUp và tornDown (không có chú thích) được gọi trong các phương thức setUp và tornDown có chú thích của lớp cơ sở.


1
không phải là một ý tưởng tồi, nhưng tôi không muốn thực thi một hợp đồng trong các bài kiểm tra không cần setUp / tornDown của riêng họ
Joel

2

Bạn có thể sử dụng @BeforeClasschú thích để đảm bảo setup()luôn được gọi đầu tiên. Tương tự, bạn có thể sử dụng @AfterClasschú thích để đảm bảo tearDown()luôn được gọi là cuối cùng.

Điều này thường không được khuyến khích, nhưng nó được hỗ trợ .

Đó không phải là chính xác những gì bạn muốn - nhưng về cơ bản nó sẽ giữ cho kết nối DB của bạn mở toàn bộ thời gian các bài kiểm tra của bạn đang chạy, và sau đó đóng lại một lần và mãi mãi.


2
Trên thực tế, nếu bạn đã làm điều này, tôi khuyên bạn nên tạo ra một phương pháp setupDB()closeDB()và đánh dấu chúng bằng @BeforeClass@AfterClassvà thay thế / trước của bạn sau khi phương pháp với setup()tearDown()
Swati

Các phương thức chú thích @BeforeClass@AfterClasscần phải tĩnh. Thế còn trường hợp, khi chúng ta muốn sử dụng các biến thể hiện bên trong các phương thức này thì sao?
Pratik Singhal

Một cảnh báo khi sử dụng @BeforeClassvới Powermock: nó chỉ hoạt động cho lần chạy thử đầu tiên. Xem vấn đề này: github.com/powermock/powermock/issues/398
Dagmar

2

Đây không phải là một câu trả lời cho câu hỏi khẩu hiệu, nhưng nó là một câu trả lời cho các vấn đề được đề cập trong phần chính của câu hỏi. Thay vì sử dụng @B Before hoặc @After, hãy xem xét sử dụng @ org.junit.Rule vì nó mang lại cho bạn sự linh hoạt hơn. ExternalResource (kể từ 4.7) là quy tắc bạn sẽ quan tâm nhất nếu bạn đang quản lý các kết nối. Ngoài ra, nếu bạn muốn thứ tự thực thi được đảm bảo cho các quy tắc của mình, hãy sử dụng RuleChain (kể từ 4.10). Tôi tin rằng tất cả những thứ này đều có sẵn khi câu hỏi này được hỏi. Ví dụ mã dưới đây được sao chép từ javadocs của ExternalResource.

 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);
     }
 }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.