@BeforeClass và kế thừa - thứ tự thực hiện


90

Tôi có một lớp cơ sở trừu tượng, lớp này tôi sử dụng làm cơ sở cho các bài kiểm tra đơn vị của mình (TestNG 5.10). Trong lớp này, tôi khởi tạo toàn bộ môi trường cho các thử nghiệm của mình, thiết lập ánh xạ cơ sở dữ liệu, v.v. Lớp trừu tượng này có một phương thức với @BeforeClasschú thích thực hiện việc khởi tạo.

Tiếp theo, tôi mở rộng lớp đó với các lớp cụ thể trong đó tôi có @Testcác phương thức và cả @BeforeClasscác phương thức. Các phương thức này thực hiện khởi tạo môi trường dành riêng cho từng lớp (ví dụ: đưa một số bản ghi vào cơ sở dữ liệu).

Làm cách nào để thực thi một thứ tự cụ thể của các @BeforeClassphương thức được chú thích? Tôi cần những cái từ lớp cơ sở trừu tượng được thực thi trước những cái của lớp mở rộng.

Thí dụ:

abstract class A {
    @BeforeClass
    doInitialization() {...}
}

class B extends A {
    @BeforeClass
    doSpecificInitialization() {...}

    @Test
    doTests() {...}
}

Thứ tự dự kiến:

A.doInitialization
B.doSpecificInitialization
B.doTests

Thực tế đặt hàng:

B.doSpecificInitialization // <- crashes, as the base init is missing
(A.doInitialization        // <---not executed
 B.doTests)                // <-/

Câu trả lời:


50

Đừng đặt @BeforeClasstrên abstractlớp. Gọi nó từ mỗi lớp con.

abstract class A {
    void doInitialization() {}
}

class B extends A {
    @BeforeClass
    void doSpecificInitialization() {
        super.doInitialization();
    }

    @Test
    void doTests() {}
}

Có vẻ như TestNG đã @BeforeClass(dependsOnMethods={"doInitialization"})- hãy thử.


9
Về cơ bản, đó là những gì tôi muốn tránh: không cần phải gọi rõ ràng các phương thức của lớp siêu (trừu tượng). Đặc biệt là tôi cũng có các lớp kế thừa từ A nhưng không có phương thức @BeforeClass riêng. Tôi chỉ phải chèn một cái cho mục đích đó.
Dominik Sandjaja

5
Cách dependsOnMethodsgiải quyết đã thực hiện thủ thuật. Mặc dù tôi muốn một "lớp cha đầu tiên" tiếp cận ...
Dominik Sandjaja

1
Để sử dụng "dependOnMethod", bạn không nên chú thích "doInitialization" bằng "@Test"? Đó là một vấn đề vì về mặt kỹ thuật nó không phải là một thử nghiệm của chính nó ...
N3da

@BeforeClass nên chú thích một phương thức tĩnh
Fabrizio Stellato

108

chỉnh sửa: Câu trả lời dưới đây là dành cho JUnit , nhưng tôi sẽ để nó ở đây, vì nó có thể hữu ích.

Theo api JUnit : "Các phương thức @BeforeClass của các lớp cha sẽ được chạy trước các lớp hiện tại."

Tôi đã thử nghiệm điều này, và nó có vẻ hiệu quả với tôi.

Tuy nhiên, như @Odys đề cập bên dưới, đối với JUnit, bạn cần có hai phương thức được đặt tên khác nhau mặc dù làm khác đi sẽ chỉ dẫn đến phương thức lớp con được chạy vì phương thức cha sẽ bị ẩn.


56
Mặc dù câu hỏi ban đầu dành cho TestNG, nhưng tôi đã đến đây sau khi tìm kiếm JUnit trên Google và câu trả lời của bạn đã giúp - cảm ơn!
teabot

9
đối với JUnit, bạn cần có hai phương thức được đặt tên khác nhau mặc dù làm khác đi sẽ dẫn đến chỉ phương thức lớp con được chạy vì phương thức cha sẽ bị ẩn.
Lẻ

2
@Odys, cảm ơn bạn rất nhiều vì đã đề cập đến điều này. Tôi đã cố gắng tìm ra lý do tại sao phương thức "thiết lập" trong lớp con của tôi đang chạy trong khi phương thức trong lớp cha của nó thì không. Bạn vừa cứu tôi một tấn trầm trọng!
Tom Catullo

Bạn đã làm nên ngày của tôi. Cảm ơn!
raiks

7

Tôi đã thêm vào publiclớp trừu tượng và TestNG (6.0.1) đã thực thi doInitialization () trước đó doTests. TestNG không thực thi doInitialization()nếu tôi xóa publickhỏi lớp A.

public abstract class A {
 @BeforeClass
 doInitialization() {...}
}

class B extends A {    
 @Test
 doTests() {...}
}

1
Điều đó đúng, nhưng không liên quan. Điều này không hoạt động khi lớp B cũng@BeforeClassphương thức -annotated, như trong trường hợp của OP.
jpaugh

1
Tôi làm giống vậy. Có vẻ như thứ tự kế thừa bị bỏ lỡ nếu phương thức cơ sở là private. Cảm ơn!
Manu

6

Tôi vừa thử ví dụ của bạn với 5.11 và tôi nhận được @BeforeClass của lớp cơ sở được gọi trước.

Bạn có thể đăng tệp testng.xml của mình không? Có thể bạn đang chỉ định cả A và B ở đó, trong khi chỉ B là cần thiết.

Vui lòng theo dõi danh sách gửi thư của testng-users và chúng tôi có thể xem xét kỹ hơn vấn đề của bạn.

- Cedric


2
Không .xml cho testng được định nghĩa (rõ ràng), nó chạy từ Eclipse và Maven.
Dominik Sandjaja

Chính xác thì bạn đang chạy nó từ Eclipse như thế nào? Nhấp chuột phải vào lớp B?
Cedric Beust

4

Tôi vừa trải qua điều này và tìm thấy một cách nữa để đạt được điều này. Chỉ cần sử dụng alwaysRuntrên @BeforeClasshoặc @BeforeMethodtrong lớp trừu tượng, hoạt động như bạn mong đợi.

public class AbstractTestClass {
    @BeforeClass(alwaysRun = true)
    public void generalBeforeClass() {
        // do stuff
        specificBeforeClass();
    }
}

2

Khi tôi chạy từ: JUnitCore.runClasses (TestClass.class); Nó sẽ thực thi phần cha đúng cách, trước phần con (Bạn không cần super.SetUpBeforeClass (); ) Nếu bạn chạy nó từ Eclipse: Vì lý do nào đó, nó không chạy được lớp cơ sở. Công việc xung quanh: Gọi lớp cơ sở một cách rõ ràng: ( BaseTest.setUpBeforeClass (); ) Bạn có thể muốn có một cờ trong lớp cơ sở trong trường hợp bạn chạy nó từ một ứng dụng, để xác định xem nó đã được thiết lập hay chưa. Vì vậy, nó chỉ chạy một lần nếu bạn chạy nó qua cả hai phương pháp có thể (chẳng hạn như từ nhật thực để thử nghiệm cá nhân và thông qua ANT cho bản phát hành bản dựng).

Đây dường như là một lỗi với Eclipse, hoặc ít nhất là kết quả không mong muốn ..


2

Đối với JUnit : Như @fortega đã đề cập: Theo api JUnit: "Các phương thức @BeforeClass của các lớp cha sẽ được chạy trước các lớp hiện tại."

Nhưng hãy lưu ý không đặt tên cả hai phương thức trùng tên . Vì trong trường hợp này, phương thức cha sẽ bị ẩn bởi phương thức con. Nguồn .


1

Làm thế nào về việc phương thức @BeforeClass của bạn gọi một phương thức specificBeforeClass () trống có thể bị các lớp con ghi đè hoặc không:

public class AbstractTestClass {
  @BeforeClass
  public void generalBeforeClass() {
    // do stuff
    specificBeforeClass();
  }

  protected void specificBeforeClass() {}
}

public class SpecificTest {
  @Override
  protected void specificBeforeClass() {
    // Do specific stuff
  }

  // Tests
}

3
BeforeClass phải tĩnh nên bạn không thể làm điều này với junit
madx

1

dependsOnMethod có thể được sử dụng.

ví dụ như trong trường hợp Spring ( AbstractTestNGSpringContextTests)

@BeforeClass(alwaysRun = true, dependsOnMethods = "springTestContextPrepareTestInstance")

1

Kiểm tra báo cáo nhập khẩu của bạn. Nó nên được

import org.testng.annotations.BeforeClass;

không phải

import org.junit.BeforeClass;


1

Điều này phù hợp với tôi -

abstract class A {
    @BeforeClass
    doInitialization() {...}
}

class B extends A {
    @Override
    @BeforeClass
    doInitialization() { 

       //do class specific init

    }   

    @Test
    doTests() {...}
}

1
Thêm một lời giải thích ngắn về những gì mã này không và trả lời câu hỏi ban đầu
Cray

0

Tại sao bạn không thử tạo một phương thức trừu tượng doSpecialInit () trong lớp siêu của bạn, được gọi từ phương thức chú thích BeforeClass của bạn trong lớp cha.

Vì vậy, các nhà phát triển kế thừa lớp của bạn buộc phải triển khai phương thức này.


Thành thật mà nói, ngay cả logic có thể đã thay đổi trong vòng 3 năm rưỡi qua kể từ khi tôi hỏi câu hỏi này ... ;-) Vì vậy, có thể đây là một ý tưởng, có thể nó không hoạt động - tôi thành thật không nhớ lại.
Dominik Sandjaja

0

Có một giải pháp dễ dàng khác ở đây.

Tình huống cụ thể của tôi là tôi cần đưa các dịch vụ giả từ "BeforeClass" vào lớp con trước khi "BeforeClass" trong lớp cha được thực thi.

Để làm điều này - chỉ cần sử dụng một @ClassRuletrong lớp con.

Ví dụ:

@ClassRule
public static ExternalResource mocksInjector = new ExternalResource() {
    @Override
    protected void before() {
        // inject my mock services here
        // Note: this is executed before the parent class @BeforeClass
    }
};

Tôi hi vọng cái này giúp được. Điều này có thể thực hiện hiệu quả thiết lập tĩnh theo thứ tự "ngược".


0

Hôm nay tôi gặp phải vấn đề tương tự, điểm khác biệt duy nhất là lớp Cơ sở không trừu tượng

Đây là trường hợp của tôi

public class A {
    @BeforeClass
    private void doInitialization() {...}
}

public class B extends A {
    @BeforeClass
    private void doSpecificInitialization() {...}

    @Test
    public void doTests() {...}
}

Đã xảy ra rằng một @BeforeClassphương thức từ lớp A không bao giờ được thực thi.

  • A.doInitialization () -> ĐIỀU NÀY KHÔNG BAO GIỜ ĐƯỢC THI HÀNH trong âm thầm
  • B.doSpecificInitialization ()
  • B.doTests ()

Chơi với các công cụ sửa đổi quyền riêng tư Tôi nhận thấy rằng TestNG sẽ không thực thi một @BeforeClassphương thức được chú thích từ lớp kế thừa nếu một phương thức không hiển thị từ lớp kế thừa

Vì vậy, điều này sẽ hoạt động:

public class A {
    @BeforeClass
    private void doInitialization() {...}
}

public class B extends A {
    @BeforeClass
    //Here a privacy modifier matters -> please make sure your method is public or protected so it will be visible for ancestors
    protected void doSpecificInitialization() {...}

    @Test
    public void doTests() {...}
}

Kết quả là sau đây xảy ra:

  • A.doInitialization ()
  • B.doSpecificInitialization ()
  • B.doTests ()

-1

Trong trường hợp của tôi (JUnit), tôi có các phương thức giống nhau được gọi là setup () trong lớp cơ sở và lớp dẫn xuất. Trong trường hợp này, chỉ có phương thức của lớp dẫn xuất được gọi và tôi gọi nó là phương thức của lớp cơ sở.


-2

Một cách tốt hơn và rõ ràng hơn để đạt được điều này bằng cách sử dụng kế thừa có thể như sau:

abstract class A {

    @BeforeClass
    void doInitialization() {}
}

class B extends A {

    @Override
    @BeforeClass
    void doInitialization() {
        super.doInitialization();
    }

    @Test
    void doTests() {}
}

1
Nếu bạn cần phương thức trong lớp Cha được thực thi trước tiên bạn chỉ cần đặt tên phương thức trong lớp Con khác với lớp Cha (vì nếu chúng có cùng chữ ký thì tính đa hình sẽ hoạt động). Tôi tin rằng đó là một cách sạch sẽ hơn.
Anatolii Stepaniuk
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.