Testng: Методы @Test из суперкласса повторно выполняются для каждого подкласса

Созданный на 10 июн. 2019  ·  12Комментарии  ·  Источник: cbeust/testng

TestNG Версия 6.14.3

Ожидаемое поведение

Методы @Test из суперкласса выполняются один раз, независимо от того, сколько подклассов наследуют суперкласс.

Фактическое поведение

Методы @Test выполняются с каждым подклассом.

Может ли проблема воспроизводиться на раннере?

  • [ ] Оболочка
  • [] Maven
  • [x] Gradle
  • [] Муравей
  • [] Затмение
  • [] IntelliJ
  • [] NetBeans

Образец тестового случая

public class BaseTest  extends SetupClass{
    @BeforeClass...
    @BeforeMethod...
    @AfterClass...
    @AfterMethod....

    <strong i="22">@Test</strong>
    public void baseMethod(){
        System.out.println("base method");
    }
}
public class Test1 extends BaseTest{
    <strong i="25">@Test</strong>
    public void test1(){
        System.out.println("test1");
    }
}
public class Test2 extends BaseTest{
    <strong i="28">@Test</strong>
    public void test2(){
        System.out.println("test2");
    }
}

Я запускаю эти тесты параллельно из XML-файла TestNG, который выглядит следующим образом:

<suite name="Regression Suite" verbose="1" parallel="classes" thread-count="4">
    <parameter name="project" value="TestPrj"/>
    <parameter name="server" value="QA01"/>
    <test name="QA01 TestPrj">
        <parameter name="environment" value="macos"/>
        <groups>
            <run>
                <include name="regression"/>
            </run>
        </groups>
        <packages>
            <package name="test"/>
        </packages>
    </test>
</suite>

PS Редактировать код в github очень плохо ... пожалуйста, перейдите к чему-нибудь другому.

inheritance

Все 12 Комментарий

@ vlad230

PS Редактировать код в github очень плохо ... пожалуйста, перейдите к чему-нибудь другому.

Возможно, вы захотите потратить время на ознакомление с Markdown. Это упростило бы добавление фрагментов кода вместе с форматированием.

Не совсем уверен, что понимаю вашу проблему здесь. В конечном итоге дочерние классы также получат методы базового класса. И если они аннотированы аннотациями TestNG, они будут выполняться для каждого дочернего класса. Так ведет себя TestNG. Итак, я бы сказал, что TestNG работает так, как задумано.

@juherr - Ваши мысли?

@krmahadevan спасибо за форматирование :)

Не совсем уверен, что понимаю вашу проблему здесь. В конечном итоге дочерние классы также получат методы базового класса. И если они аннотированы аннотациями TestNG, они будут выполняться для каждого дочернего класса. Так ведет себя TestNG. Итак, я бы сказал, что TestNG работает так, как задумано.

Если вы спросите меня, было бы бессмысленно выполнять тесты в суперклассе несколько раз.
Если это ожидаемое поведение TestNG прямо сейчас, возможно, мы могли бы добавить это в качестве улучшения.

Работает как задумано.
Почему бы вам не переместить baseMethod test в отдельный класс?

@ vlad230

Если вы спросите меня, было бы бессмысленно выполнять тесты в суперклассе несколько раз.

Чего именно вы пытаетесь достичь? Было бы здорово, если бы вы могли помочь разработать или объяснить свой сценарий.

Если это ожидаемое поведение TestNG прямо сейчас, возможно, мы могли бы добавить это в качестве улучшения.

Не уверен, что это будет воспринято как улучшение, потому что это противоречит интуиции. Если вы в основном хотите, чтобы метод базового класса выполнялся ровно один раз, независимо от того, сколько дочерних классов присутствует, вы можете сделать это сегодня в своем тестовом проекте, выполнив следующие

  1. Сделайте так, чтобы ваш базовый класс реализовал org.testng.IHookable
  2. В его метод run() добавьте проверку редактирования, которая проверяет, выполняется ли уже метод базового класса, и запускает его, если не выполняется.

Вот образец

Интерфейс маркера, указывающий, что метод базового класса должен выполняться только один раз.

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({METHOD, TYPE})
public <strong i="20">@interface</strong> RunOnce {
}

Вот синглтон слежения за состоянием

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class StateTracker {

  private static final StateTracker instance = new StateTracker();

  private final Map<String, Object> runOnceData = new ConcurrentHashMap<>();

  private StateTracker() {}

  public static StateTracker getInstance() {
    return instance;
  }

  boolean canExecute(Method method) {
    RunOnce runOnce = method.getAnnotation(RunOnce.class);
    String methodName = method.getName();
    boolean execute = true;
    if (runOnce != null) {
      if (runOnceData.containsKey(methodName)) {
        execute = false;
      } else {
        runOnceData.put(methodName, new Object());
      }
    }
    return execute;
  }
}

Вот как будет выглядеть базовый класс

import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;
import org.testng.annotations.Test;

public class BaseTest implements IHookable {

  <strong i="27">@Test</strong>
  <strong i="28">@RunOnce</strong>
  public void baseclassTestMethod() {
    System.err.println("Base class test method executed");
  }

  <strong i="29">@Override</strong>
  public void run(IHookCallBack callBack, ITestResult testResult) {
    boolean execute =
        StateTracker.getInstance()
            .canExecute(testResult.getMethod().getConstructorOrMethod().getMethod());
    if (execute) {
      callBack.runTestMethod(testResult);
    } else {
      testResult.setStatus(ITestResult.SKIP);
      testResult.setThrowable(new RuntimeException("Intentionally skipping"));
    }
  }
}

Вот как будут выглядеть дочерние классы

import org.testng.annotations.Test;

public class ChildClass1 extends BaseTest {

  <strong i="33">@Test</strong>
  public void childTest() {
    System.err.println(getClass().getName() + ".childTest() executed");
  }
}
import org.testng.annotations.Test;

public class ChildClass2 extends BaseTest {

  <strong i="36">@Test</strong>
  public void childTest() {
    System.err.println(getClass().getName() + ".childTest() executed");
  }
}

Вам нужно будет добавить некоторый механизм фильтрации, который сокращает результаты тестов и удаляет пропущенные тесты, которые аннотируются с помощью @RunOnce

@krmahadevan
Чего именно вы пытаетесь достичь? Было бы здорово, если бы вы могли помочь разработать или объяснить свой сценарий.

Я пытаюсь разделить тесты на несколько классов (связанных с функцией). Например, у вас есть «базовый класс», в котором есть часть настройки для этой функции с методами @Before и @After наряду с другими вспомогательными методами (не тестами), которые поддерживают тесты из базового класса вместе с тестами в «дочерних классах». ". Таким образом, вы просто добавляете новые тесты в свои дочерние классы, а не дублируете код в дочерних классах.
Базовый класс будет иметь некоторые методы тестирования, которые проверяют более общие части этой функции, а дочерние классы будут иметь более конкретные тесты.
Поскольку я пытаюсь оптимизировать время выполнения теста путем параллельного запуска классов, мне пришлось разделить мой исходный «базовый класс», который имел более 50 тестовых методов, на несколько меньших «дочерних классов», чтобы эти меньшие классы распределялись по другим потокам. / исполнители.

@krmahadevan большое спасибо за подробное решение, но мне оно кажется немного громоздким. Поскольку TestNG можно настроить, я бы предпочел рассматривать это как поведение по умолчанию.
Я не вижу необходимости в постоянном повторном выполнении тестов из базового класса. Если это вопрос настройки / разборки, всегда можно использовать @ Before / @After , который будет повторно выполняться каждый раз перед дочерним классом из базового класса.

Да, я думаю, что поведение можно настроить.

Фактически, я не нашел варианта использования, когда мы хотели бы запускать родительский тест для каждого дочернего экземпляра.
Если у вас нет прямой зависимости от тестирования ( dependsOnMethods ), потому что состояние теста часто изменяется таким же образом в интеграционных тестах.

@krmahadevan WDYT?

@ vlad230

Большое спасибо за подробное решение, но мне оно кажется немного громоздким

Ваш вариант использования также немного отличается от обычного. Более того, я привел вам рабочий пример того, как это можно сделать. Все, что вам нужно сделать, это просто принять это и опираться на это.

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

Нет, на мой взгляд, это невозможно. Тесты предназначены для постоянного выполнения. То, что они находятся в базовом классе, не означает, что они должны выполняться выборочно. TestNG можно настроить. Но это не значит, что TestNG должен поддерживать все настройки. Ему просто нужно предоставить способы, с помощью которых пользователь сможет настроить его в соответствии со своими потребностями и работать с ним. Образец, которым я поделился, является примером применения этих настроек без внесения изменений в TestNG.

Я не вижу необходимости в постоянном повторном выполнении тестов из базового класса. Если это вопрос настройки / разборки, всегда можно использовать @ Before / @after , который будет повторно выполняться каждый раз перед дочерним классом из базового класса.

Для меня это всего лишь один взгляд на вещи. Если конкретный метод не должен выполняться каждый раз, вам не следует использовать аннотацию @Test . Вы могли бы очень хорошо использовать аннотации конфигурации, такие как @BeforeTest (который выполняется только один раз за тег <test> ) или @BeforeSuite (который выполняется только один раз за <suite> tag) нет? Разве они не сработают на вас.

Я не уверен, действительно ли ваши тестовые методы в вашем базовом классе являются тестовыми методами. Для меня они больше похожи на конфигурации, которые устанавливают условия настройки, которые должны быть выполнены для прохождения теста.

@juherr - я не совсем уверен, что согласен с приведенным здесь вариантом использования. Тест независимо от того, где он был обнаружен, предназначен для выполнения. Если есть сценарий, в котором мы не хотим, чтобы он выполнялся для всех дочерних классов, тогда мы должны использовать аннотации конфигурации, а не аннотацию @Test .

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

Более того, я чувствую, что в настоящее время в TestNG уже есть способ сделать это (которым я поделился в качестве примера). Почему бы просто не воспользоваться этим? Кажется, что это разовый вариант использования, и он, похоже, не вписывается в обычный способ использования TestNG.

@krmahadevan Мне очень жаль, но я думаю, вы не поняли, что я пытаюсь сделать.
В моем родительском классе (BaseTest - например, DashboardTest) у меня есть 15 тестов, и у меня есть 3 дочерних класса (например, DashboardFilterTest, DashboardTreeTest, DashboardEditorTest), внутри каждого из которых есть тесты. Используя подход, который я описал в своем сообщении, каждый раз, когда я запускаю тесты, эти 15 тестов выполняются 3 раза (так что выполняется 3x15 = 45 тестов), не принося никакой ценности и бесполезно увеличивая время выполнения, в отличие от простого выполнения 15 тестов один раз.

Я по-прежнему думаю, что не имеет смысла повторно выполнять тесты, расположенные в родительском классе, каждый раз, когда выполняются тесты из дочернего класса. Предпочел бы поведение TestNG по умолчанию для однократного выполнения тестовых методов из родительского класса, независимо от количества дочерних классов, которые его расширяют.

Если это невозможно, я понимаю. Спасибо, в любом случае.

@ vlad230

Спасибо за добавление дополнительного контекста.

В моем родительском классе (BaseTest - например, DashboardTest) у меня есть 15 тестов, и у меня есть 3 дочерних класса (например, DashboardFilterTest, DashboardTreeTest, DashboardEditorTest), внутри каждого из которых есть тесты. Используя подход, который я описал в своем сообщении, каждый раз, когда я запускаю тесты, эти 15 тестов выполняются 3 раза (так что выполняется 3x15 = 45 тестов), не принося никакой ценности и бесполезно увеличивая время выполнения, в отличие от простого выполнения 15 тестов один раз.

В таком случае, почему DashboardFilterTest, DashboardTreeTest, DashboardEditorTest расширяет DashboardTest ? Похоже, что DashboardTest тесты должны находиться в собственном классе вместо того, чтобы вы использовали подход наследования здесь, нет? Базовый класс следует реорганизовать, чтобы удалить все общие методы тестирования и разместить его в отдельном тестовом классе, а DashboardTest следует отремонтировать, чтобы разместить только общие методы, отличные от @Test нет?

Если это невозможно, я понимаю. Спасибо, в любом случае.

Дело не в том, можно ли это сделать или нет (проблемы реализации - это отдельная тема), но мне все еще трудно понять обоснованность самого варианта использования.

@krmahadevan

В таком случае, почему DashboardFilterTest, DashboardTreeTest, DashboardEditorTest расширяет DashboardTest ? Похоже, что DashboardTest тесты должны находиться в собственном классе вместо того, чтобы вы использовали подход наследования здесь, нет? Базовый класс следует реорганизовать, чтобы удалить все общие методы тестирования и разместить его в отдельном тестовом классе, а DashboardTest следует отремонтировать, чтобы разместить только общие методы, отличные от @Test нет?

«DashboardTest» содержит вспомогательные методы (например, openDashboard (), createDashboard () и т. Д.) И методы установки / удаления / очистки (@ Before / @ After), которые также необходимы для дочерних классов. Так что мне не нужно копировать код в дочерних классах.
Да, некоторые тесты в 'DashboardTest' можно было бы перенести в некоторые другие конкретные классы, но я хотел оставить их здесь, поскольку они являются более общими (например, проверка общего вида приборной панели, базовые тесты CRUD или даже некоторые тесты, которые этого не сделали) t вписываются в определенную категорию или представляют собой смесь).

Конечно, у меня мог бы быть тестовый класс, отличный от @ Test (хотя это было бы странно, и кто-то мог бы добавить сюда тесты по ошибке), но это было бы просто, чтобы избежать этой проблемы, верно?

@ vlad230

«DashboardTest» содержит вспомогательные методы (например, openDashboard (), createDashboard () и т. Д.) И методы установки / удаления / очистки (@ До / @ после), которые также необходимы для дочерних классов. Так что мне не нужно копировать код в дочерних классах.

Это имеет смысл. и да, они вполне могут находиться в базовом классе, так что методы конфигурации также доступны для дочерних классов.

Да, некоторые тесты в 'DashboardTest' можно было бы перенести в некоторые другие конкретные классы, но я хотел оставить их здесь, поскольку они являются более общими (например, проверка общего вида приборной панели, базовые тесты CRUD или даже некоторые тесты, которые этого не сделали) t вписываются в определенную категорию или представляют собой смесь).

Вы по-прежнему можете создать класс с именем GenericDashboardTest extends DashboardTest который содержит все общие методы @Test .

Конечно, я мог бы иметь тестовый класс, отличный от @ test (хотя это было бы странно, и кто-то мог бы добавить сюда тесты по ошибке), но это было бы просто, чтобы избежать этой проблемы, верно?

Ну, разве не для этого нужны обзоры кода :) Чтобы предотвратить такие проскальзывания. Базовый класс (из того, что вы объяснили) не обязательно должен содержать общие универсальные методы @Test . Он может очень хорошо содержать только помощников и конфигурации, и у вас может быть еще один тестовый класс, который расширяет базовый класс и содержит общие методы тестирования. Это должно разрешить возникшую путаницу.

Закрытие этой проблемы с решением "как _работает как задумано_"

Была ли эта страница полезной?
0 / 5 - 0 рейтинги