Testng: Los métodos @Test de la superclase se vuelven a ejecutar para cada subclase

Creado en 10 jun. 2019  ·  12Comentarios  ·  Fuente: cbeust/testng

TestNG Versión 6.14.3

Comportamiento esperado

Los métodos @Test de la superclase se ejecutan una vez sin importar cuántas subclases hereden la superclase.

Comportamiento real

Los métodos @Test se ejecutan con cada subclase.

¿Es reproducible el problema en el corredor?

  • [ ] Cascarón
  • [] Maven
  • [x] Gradle
  • [] Hormiga
  • [] Eclipse
  • [] IntelliJ
  • [] NetBeans

Ejemplo de caso de prueba

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

Estoy ejecutando estas pruebas en paralelo desde un archivo xml TestNG que se ve así:

<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>

PD Editar código en problemas de github es increíblemente malo ... muévase a otra cosa.

inheritance

Todos 12 comentarios

@ vlad230

PD Editar código en problemas de github es increíblemente malo ... muévase a otra cosa.

Es posible que desee dedicar algún tiempo a familiarizarse con Markdown. Eso facilitaría mucho la adición de fragmentos de código junto con el formato.

No estoy seguro de entender su problema aquí. En última instancia, las clases secundarias también obtendrían los métodos de la clase base. Y si se anotan con anotaciones de TestNG, se ejecutarán para cada clase secundaria. Ese es el comportamiento de TestNG. Entonces diría que TestNG está funcionando según lo diseñado.

@juherr - ¿

@krmahadevan gracias por el formato :)

No estoy seguro de entender su problema aquí. En última instancia, las clases secundarias también obtendrían los métodos de la clase base. Y si se anotan con anotaciones de TestNG, se ejecutarán para cada clase secundaria. Ese es el comportamiento de TestNG. Entonces diría que TestNG está funcionando según lo diseñado.

Si me preguntas, no tendría ningún sentido ejecutar las pruebas en la superclase varias veces.
Si este es el comportamiento esperado de TestNG en este momento, tal vez podríamos agregar esto como una mejora.

Funcionando según lo diseñado.
¿Por qué no mueves la prueba baseMethod en su propia clase?

@ vlad230

Si me preguntas, no tendría ningún sentido ejecutar las pruebas en la superclase varias veces.

¿Qué estás tratando de lograr exactamente? Sería genial si pudiera ayudar a elaborar o explicar su escenario.

Si este es el comportamiento esperado de TestNG en este momento, tal vez podríamos agregar esto como una mejora.

No estoy seguro de que se considere una mejora porque es contrario a la intuición. Si básicamente desea que el método de la clase base se ejecute exactamente una vez, sin importar cuántas clases secundarias estén presentes, puede hacerlo hoy en su proyecto de prueba haciendo lo siguiente

  1. Haga que su clase base implemente org.testng.IHookable
  2. Dentro de su método run() , agregue una verificación de edición que verifique si el método de la clase base ya está ejecutado y lo ejecutará si no se ejecuta.

Aquí hay una muestra

Interfaz de marcador que indica que un método de clase base debe ejecutarse solo una vez

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 {
}

Aquí está el rastreador de estado singleton

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

Así es como se vería la clase base

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

Así es como se verían las clases para niños

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

Debería agregar algún mecanismo de filtrado que elimine los resultados de la prueba y elimine las pruebas omitidas que se anotan con @RunOnce

@krmahadevan
¿Qué estás tratando de lograr exactamente? Sería genial si pudiera ayudar a elaborar o explicar su escenario.

Estoy tratando de separar las pruebas en varias clases (que están relacionadas con una función). Por ejemplo, usted tiene una "clase base" que tiene la parte de configuración para esa característica con los métodos @Before y @After a lo largo de otros métodos de ayuda laterales (no pruebas) que soportan las pruebas de la clase base junto a las pruebas en las "clases hijas ". Entonces, de esta manera, simplemente agregaría nuevas pruebas a sus clases secundarias y no duplicaría el código en las clases secundarias.
La clase base tendría algunos métodos de prueba que prueban partes más generales de esa característica y las clases secundarias tendrán pruebas más específicas.
Como estoy tratando de optimizar el tiempo de ejecución de la prueba ejecutando clases en paralelo, tuve que dividir mi "clase base" original, que tenía más de 50 métodos de prueba en múltiples "clases secundarias" más pequeñas, de modo que estas clases más pequeñas se distribuyen a otros subprocesos / ejecutores.

@krmahadevan muchas gracias por la solución detallada, pero me parece un poco engorrosa. Dado que TestNG se puede personalizar, preferiría ver esto como el comportamiento predeterminado.
No veo la necesidad de volver a ejecutar constantemente las pruebas de la clase base. Si se trata de configuración / desmontaje, siempre se puede usar @ Before / @After que se volvería a ejecutar cada vez que se realizara una clase secundaria de la clase base.

Sí, creo que el comportamiento podría configurarse.

De hecho, no encuentro un caso de uso en el que queramos ejecutar la prueba principal para cada instancia secundaria.
A menos que tenga una dependencia directa para probar ( dependsOnMethods ) porque el estado de prueba a menudo se modifica así en las pruebas de integración.

@krmahadevan WDYT?

@ vlad230

muchas gracias por la solución detallada pero me parece un poco engorrosa

Su caso de uso también se ve un poco diferente al habitual. Además, les he dado un ejemplo práctico de cómo se puede hacer. Todo lo que necesita hacer es adoptar esto y construir sobre esto.

Dado que TestNG se puede personalizar, preferiría ver esto como el comportamiento predeterminado.

No. Eso no se puede hacer en mi opinión. Las pruebas están diseñadas para ejecutarse todo el tiempo. El hecho de que residan en una clase base no significa que deban ejecutarse de forma selectiva. TestNG se puede personalizar. Pero eso no significa que TestNG deba admitir todas las personalizaciones. Simplemente necesita proporcionar formas en las que un usuario pueda personalizarlo según sus necesidades y trabajar con él. La muestra que compartí es un ejemplo de cómo aplicar esas personalizaciones sin que TestNG tenga que someterse a cambios.

No veo la necesidad de volver a ejecutar constantemente las pruebas de la clase base. Si se trata de configuración / desmontaje, siempre se puede usar @ Before / @after que se volvería a ejecutar cada vez antes de una clase secundaria de la clase base.

Para mí, esa es solo una forma de ver las cosas. Si un método en particular no debe ejecutarse cada vez, entonces no debe usar la anotación @Test . Puede usar las anotaciones de configuración como @BeforeTest (que se ejecuta solo una vez por <test> etiqueta) o @BeforeSuite (que se ejecuta solo una vez por <suite> etiqueta) ¿no? ¿No funcionarían para ti?

No estoy seguro, si sus métodos de prueba en su clase base son realmente métodos de prueba. Para mí, suenan más como configuraciones que establecen las condiciones de configuración que deben cumplirse para que se ejecute la prueba.

@juherr : no estoy muy seguro de estar de acuerdo con el caso de uso aquí. Una prueba, independientemente de dónde se encuentre, debe ejecutarse. Si hay un escenario en el que no queremos que se ejecute para todas las clases secundarias, entonces deberíamos usar las anotaciones de configuración y no la anotación @Test .

En el lado de la implementación, creo que esto agregará mucho caos al código base, especialmente cuando la gente comience a recurrir al uso de grupos como medio de ejecución.

Además, creo que ya hay una forma de hacer esto actualmente dentro de TestNG (que he compartido como ejemplo). ¿Por qué no aprovechar eso? Este parece ser un caso de uso único y no parece encajar en la forma habitual en que se usa TestNG.

@krmahadevan Lo siento, pero creo que no entendiste lo que estoy tratando de hacer.
En mi clase principal (BaseTest - por ejemplo, DashboardTest) tengo 15 pruebas ahora y tengo 3 clases secundarias (por ejemplo, DashboardFilterTest, DashboardTreeTest, DashboardEditorTest) que cada una tiene pruebas dentro. Usando el enfoque que describí en mi publicación, cada vez que ejecuto las pruebas, esas 15 pruebas se ejecutan 3 veces (por lo tanto, 3x15 = 45 pruebas en ejecución) no aportan ningún valor y extienden el tiempo de ejecución inútilmente, en lugar de simplemente ejecutar el 15 pruebas una vez.

Sigo pensando que no tiene sentido que las pruebas que se encuentran en una clase principal se vuelvan a ejecutar cada vez que se ejecutan las pruebas de una clase secundaria. Preferiría el comportamiento predeterminado de TestNG para ejecutar métodos de prueba desde una clase principal una vez, independientemente del número de clases secundarias que la extienden.

Si esto no se puede hacer, lo entiendo. Gracias de cualquier manera.

@ vlad230

Gracias por agregar contexto adicional.

En mi clase principal (BaseTest - por ejemplo, DashboardTest) tengo 15 pruebas ahora y tengo 3 clases secundarias (por ejemplo, DashboardFilterTest, DashboardTreeTest, DashboardEditorTest) que cada una tiene pruebas dentro. Usando el enfoque que describí en mi publicación, cada vez que ejecuto las pruebas, esas 15 pruebas se ejecutan 3 veces (por lo tanto, 3x15 = 45 pruebas en ejecución) no aportan ningún valor y extienden el tiempo de ejecución inútilmente, en lugar de simplemente ejecutar el 15 pruebas una vez.

En ese caso, ¿por qué DashboardFilterTest, DashboardTreeTest, DashboardEditorTest estaría extendiendo DashboardTest ? Parece que las pruebas de DashboardTest deben residir en su propia clase en lugar de que usted aproveche un enfoque de herencia aquí, ¿no? La clase base debe refactorizarse para eliminar todos los métodos de prueba comunes y alojarse en una clase de prueba separada, y DashboardTest debe refactorizarse para albergar métodos comunes que no sean @Test , ¿no?

Si esto no se puede hacer, lo entiendo. Gracias de cualquier manera.

No se trata de si se pueden hacer o no (los desafíos de implementación son un tema aparte), pero todavía me cuesta entender la validez del caso de uso en sí.

@krmahadevan

En ese caso, ¿por qué DashboardFilterTest, DashboardTreeTest, DashboardEditorTest estaría extendiendo DashboardTest ? Parece que las pruebas de DashboardTest deben residir en su propia clase en lugar de que usted aproveche un enfoque de herencia aquí, ¿no? La clase base debe refactorizarse para eliminar todos los métodos de prueba comunes y alojarse en una clase de prueba separada, y DashboardTest debe refactorizarse para albergar métodos comunes que no sean @Test , ¿no?

'DashboardTest' contiene métodos auxiliares (por ejemplo, openDashboard (), createDashboard (), etc.) y métodos de configuración / desmontaje / limpieza (@ Before / @ After) que también son necesarios para las clases secundarias. Entonces, no necesito replicar el código en las clases secundarias.
Sí, algunas de las pruebas en 'DashboardTest' podrían trasladarse a otras clases específicas, pero quería mantenerlas aquí ya que son más generales (por ejemplo, verificando el aspecto general del tablero, pruebas CRUD básicas o incluso algunas pruebas que no lo hicieron ' t encajan en una categoría específica o son una mezcla).

Claro, podría tener una clase de prueba que no sea @ Test (aunque sería extraño y alguien podría agregar pruebas aquí por error) pero sería solo para evitar este problema, ¿verdad?

@ vlad230

'DashboardTest' contiene métodos auxiliares (por ejemplo, openDashboard (), createDashboard (), etc.) y métodos de configuración / desmontaje / limpieza (@ Before / @ after) que también son necesarios para las clases secundarias. Entonces, no necesito replicar el código en las clases secundarias.

Esto tiene sentido. y sí, pueden residir muy bien dentro de la clase base para que los métodos de configuración también estén disponibles para las clases secundarias.

Sí, algunas de las pruebas en 'DashboardTest' podrían trasladarse a otras clases específicas, pero quería mantenerlas aquí ya que son más generales (por ejemplo, verificando el aspecto general del tablero, pruebas CRUD básicas o incluso algunas pruebas que no lo hicieron ' t encajan en una categoría específica o son una mezcla).

Aún puede crear una clase llamada GenericDashboardTest extends DashboardTest que contenga todos los métodos @Test genéricos.

Claro, podría tener una clase de prueba que no sea @ prueba (aunque sería extraño y alguien podría agregar pruebas aquí por error) pero sería solo para evitar este problema, ¿verdad?

Bueno, ¿no es eso para lo que están las revisiones de código? :) Para evitar tales deslizamientos. La clase base (de lo que explicó) no necesita contener los métodos @Test genéricos comunes. Puede albergar muy bien solo los ayudantes y las configuraciones, y puede tener una clase de prueba más que amplíe la clase base y albergue los métodos de prueba genéricos. Eso debería resolver la confusión actual.

Cerrando este problema con una resolución como _funcionando según lo diseñado_

¿Fue útil esta página
0 / 5 - 0 calificaciones