@BeforeClass и наследование - порядок выполнения


93

У меня есть абстрактный базовый класс, который я использую в качестве основы для своих модульных тестов (TestNG 5.10). В этом классе я инициализирую всю среду для своих тестов, настраивая сопоставления базы данных и т. Д. Этот абстрактный класс имеет метод с @BeforeClassаннотацией, который выполняет инициализацию.

Затем я расширяю этот класс конкретными классами, в которых у меня есть @Testметоды, а также @BeforeClassметоды. Эти методы выполняют инициализацию среды, зависящую от класса (например, помещают некоторые записи в базу данных).

Как я могу обеспечить определенный порядок @BeforeClassаннотированных методов? Мне нужно, чтобы те из абстрактного базового класса выполнялись раньше, чем из расширяющегося класса.

Пример:

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

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

    @Test
    doTests() {...}
}

Ожидаемый заказ:

A.doInitialization
B.doSpecificInitialization
B.doTests

Фактический заказ:

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

Ответы:


50

Не ставить @BeforeClassна abstractклассе. Вызовите его из каждого подкласса.

abstract class A {
    void doInitialization() {}
}

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

    @Test
    void doTests() {}
}

Похоже, у TestNG есть @BeforeClass(dependsOnMethods={"doInitialization"})- попробуйте.


10
В основном это то, чего я хотел избежать: не нужно явно вызывать методы супер (абстрактного) класса. Тем более, что у меня есть классы, которые наследуются от A, но не имеют собственного метода @BeforeClass. Мне пришлось бы вставить один только для этой цели.
Dominik Sandjaja

5
dependsOnMethodsОбходной путь сделал трюк. Хотя я бы предпочел подход «сначала суперкласс» ...
Доминик Санджая

1
Чтобы использовать «dependsOnMethod», не следует ли «doInitialization» аннотировать с «@Test»? Это проблема, поскольку технически это не тест сам по себе ...
N3da

@BeforeClass должен аннотировать статический метод
Фабрицио

110

edit: ответ ниже для JUnit , но я все равно оставлю его здесь, потому что он может быть полезен.

Согласно JUnit api : «Методы суперклассов @BeforeClass будут выполняться раньше, чем методы текущего класса».

Я тестировал это, и, похоже, у меня это работает.

Однако, как упоминает @Odys ниже, для JUnit вам нужно, чтобы два метода назывались по-разному, хотя в противном случае будет запущен только метод подкласса, потому что родительский элемент будет затенен.


56
Несмотря на то, что исходный вопрос был для TestNG, я пришел сюда после поиска в Google для JUnit, и ваш ответ помог - спасибо!
teabot

10
для JUnit вам нужно, чтобы два метода назывались по-разному, хотя в противном случае будет запущен только метод подкласса, потому что родительский будет затенен.
Odys,

3
@Odys, большое спасибо, что упомянули об этом. Я изо всех сил пытался понять, почему метод "setup" в моем подклассе работал, а метод в его суперклассе - нет. Вы только что спасли меня от обиды!
Том

Вы сделали мой день. Благодарность!
Райкс

7

Я добавил publicв абстрактный класс, и TestNG (6.0.1) выполнил doInitialization () раньше doTests. TestNG не выполняется, doInitialization()если я удалю его publicиз класса A.

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

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

1
Это правда, но к делу не относится. Это не работает, когда класс B также имеет @BeforeClassаннотированный метод, как в случае OP.
jpaugh

1
Я сделал то же самое. Кажется, что порядок наследования отсутствует, если базовый метод является частным. Благодарность!
Manu

6

Я только что попробовал ваш пример с 5.11, и сначала я вызываю @BeforeClass базового класса.

Вы можете опубликовать свой файл testng.xml? Возможно, вы указываете там как A, так и B, а необходим только B.

Не стесняйтесь следить за списком рассылки testng-users, и мы сможем более подробно рассмотреть вашу проблему.

- Седрик


2
Нет .xml для testng определен (явно), он запускается из Eclipse и Maven.
Доминик Санджая,

Как именно вы его запускаете из Eclipse? Щелкнув правой кнопкой мыши по классу B?
Седрик Беуст,

4

Я только что прошел через это и нашел еще один способ добиться этого. Просто используйте alwaysRunна @BeforeClassили @BeforeMethodв абстрактном классе, работает , как и следовало ожидать.

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

2

Когда я бегу из: JUnitCore.runClasses (TestClass.class); Он правильно выполнит родительский элемент перед дочерним (вам не нужен super.SetUpBeforeClass (); ) Если вы запустите его из Eclipse: по какой-то причине он не может запустить базовый класс. Решение проблемы : явным образом вызовите базовый класс: ( BaseTest.setUpBeforeClass (); ) Возможно, вы захотите иметь флаг в базовом классе, если вы запускаете его из приложения, чтобы определить, настроен он уже или нет. Таким образом, он запускается только один раз, если вы запускаете его обоими возможными методами (например, из eclipse для личного тестирования и через ANT для выпуска сборки).

Похоже, это ошибка Eclipse или, по крайней мере, неожиданные результаты.


2

Для JUnit : Как упомянул @fortega: Согласно JUnit api: «Методы суперклассов @BeforeClass будут выполняться раньше, чем методы текущего класса».

Но будьте осторожны, не называйте оба метода одинаковыми именами . Поскольку в этом случае родительский метод будет скрыт дочерним родителем. Источник .


1

Как насчет того, чтобы ваш метод @BeforeClass вызывал пустой метод specificBeforeClass (), который может или не может быть перезаписан подклассами, например:

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 должен быть статическим, поэтому вы не можете сделать это с помощью junit
madx

1

dependsOnMethod может быть использован.

например, в случае Spring ( AbstractTestNGSpringContextTests)

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

1

Проверьте свой оператор импорта. Должен быть

import org.testng.annotations.BeforeClass;

не

import org.junit.BeforeClass;


1

Это работает для меня -

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

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

       //do class specific init

    }   

    @Test
    doTests() {...}
}

1
Добавьте краткое объяснение того, что делает этот код, и
Cray

0

Почему бы вам не попытаться создать абстрактный метод doSpecialInit () в своем суперклассе, вызываемый из вашего аннотированного метода BeforeClass в суперклассе.

Поэтому разработчики, наследующие ваш класс, вынуждены реализовать этот метод.


Честно говоря, даже логика могла измениться за последние 3 1/2 года с тех пор, как я задал этот вопрос ... ;-) Так что да, возможно, это была идея, а может, она не сработала - честно говоря, я не помнить.
Dominik Sandjaja 05

0

Здесь есть еще одно простое решение.

Моя конкретная ситуация заключается в том, что мне нужно внедрить фиктивные службы из «BeforeClass» в подкласс до выполнения «BeforeClass» в суперклассе.

Для этого просто используйте a @ClassRuleв подклассе.

Например:

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

Надеюсь, это поможет. Это может эффективно выполнять статическую настройку в «обратном» порядке.


0

Сегодня я столкнулся с подобной проблемой, единственная разница заключалась в том, что базовый класс не был абстрактным

Вот мой случай

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

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

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

Оказалось, что @BeforeClassметод из класса A никогда не выполнялся.

  • A.doInitialization () -> ЭТО НИКОГДА НЕ ВЫПОЛНЯЛОСЬ тихо
  • B.doSpecificInitialization ()
  • B.doTests ()

Играя с модификаторами секретности я обнаружил , что TestNG не будет выполняться в @BeforeClassаннотированный метод из унаследованного класса , если метод не видно из класса-наследодателя

Итак, это будет работать:

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() {...}
}

В результате происходит следующее:

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

-1

В моем случае (JUnit) у меня есть те же методы, которые называются setup () в базовом и производном классах. В этом случае вызывается только метод производного класса, и он вызывает метод базового класса.


-2

Лучший и более чистый способ добиться этого с использованием наследования может быть следующим:

abstract class A {

    @BeforeClass
    void doInitialization() {}
}

class B extends A {

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

    @Test
    void doTests() {}
}

1
Если вам нужно, чтобы сначала выполнялся метод в родительском классе, вам просто нужно назвать метод в дочернем классе иначе, чем у родительского (потому что, если они имеют одинаковую сигнатуру, то полиморфизм вступает в действие). Я считаю, что это более чистый способ.
Анатолий Степанюк
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.