Testng: Os métodos @Test da superclasse são executados novamente para cada subclasse

Criado em 10 jun. 2019  ·  12Comentários  ·  Fonte: cbeust/testng

TestNG Versão 6.14.3

Comportamento esperado

Os métodos @Test da superclasse são executados uma vez, não importa quantas subclasses herdem a superclasse.

Comportamento real

Os métodos @Test são executados com cada subclasse.

O problema pode ser reproduzido no runner?

  • [ ] Concha
  • [] Maven
  • [x] Gradle
  • [] Formiga
  • [] Eclipse
  • [] IntelliJ
  • [] NetBeans

Amostra de caso de teste

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

Estou executando esses testes em paralelo a partir de um arquivo xml TestNG semelhante a este:

<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 Editar código em questões de github é incrivelmente ruim ... por favor, vá para outra coisa.

inheritance

Todos 12 comentários

@ vlad230

PS Editar código em questões de github é incrivelmente ruim ... por favor, vá para outra coisa.

Você pode querer gastar algum tempo se familiarizando com o Markdown. Isso tornaria muito fácil adicionar trechos de código junto com a formatação.

Não tenho certeza se entendi seu problema aqui. Em última análise, as classes filhas também obteriam os métodos da classe base. E se eles forem anotados com anotações TestNG, eles serão executados para cada classe filha. Esse é o comportamento do TestNG. Então, eu diria que o TestNG está funcionando conforme projetado.

@juherr - Seus pensamentos?

@krmahadevan obrigado pela formatação :)

Não tenho certeza se entendi seu problema aqui. Em última análise, as classes filhas também obteriam os métodos da classe base. E se eles forem anotados com anotações TestNG, eles serão executados para cada classe filha. Esse é o comportamento do TestNG. Então, eu diria que o TestNG está funcionando conforme projetado.

Se você me perguntar, não faria sentido executar os testes na superclasse várias vezes.
Se este for o comportamento esperado do TestNG agora, talvez pudéssemos adicionar isso como uma melhoria.

Funcionando conforme projetado.
Por que você não move o teste baseMethod em sua própria classe?

@ vlad230

Se você me perguntar, não faria sentido executar os testes na superclasse várias vezes.

O que exatamente você está tentando alcançar? Seria ótimo se você pudesse ajudar a elaborar ou explicar seu cenário.

Se este for o comportamento esperado do TestNG agora, talvez pudéssemos adicionar isso como uma melhoria.

Não tenho certeza de que isso seria considerado um aprimoramento porque é contra-intuitivo. Se você basicamente deseja que o método da classe base seja executado exatamente uma vez, não importa quantas classes filhas estejam presentes, você poderia fazê-lo hoje em seu projeto de teste, fazendo o seguinte

  1. Faça com que sua classe base implemente org.testng.IHookable
  2. Dentro de seu método run() , adicione uma verificação de edição que verificaria se o método da classe base já foi executado e execute-o se não for executado.

Aqui está uma amostra

Interface de marcador que indica que um método de classe base deve ser executado apenas uma 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 {
}

Aqui está o singleton do rastreador de estado

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

Esta é a aparência da classe 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"));
    }
  }
}

É assim que as classes filhas seriam

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

Você precisaria adicionar algum mecanismo de filtragem que limpe os resultados do teste e remova os testes ignorados que são anotados usando @RunOnce

@krmahadevan
O que exatamente você está tentando alcançar? Seria ótimo se você pudesse ajudar a elaborar ou explicar seu cenário.

Estou tentando separar os testes em várias classes (que estão relacionadas a um recurso). Por exemplo, você tem uma "classe base" que tem a parte de configuração para esse recurso com métodos @Before & @After ao longo outros métodos auxiliares secundários (não testes) que suportam os testes da classe base ao lado dos testes nas "classes criança " Portanto, dessa forma, você apenas adicionaria novos testes às classes filhas e não duplicaria o código nas classes filhas.
A classe base teria alguns métodos de teste que testam partes mais gerais desse recurso e as classes filhas terão testes mais específicos.
Como estou tentando otimizar o tempo de execução do teste executando classes em paralelo, tive que dividir minha "classe base" original, que tinha mais de 50 métodos de teste em várias "classes filhas" menores, de modo que essas classes menores fossem distribuídas para outros threads / executores.

@krmahadevan muito obrigado pela solução detalhada, mas parece um pouco complicado para mim. Como o TestNG pode ser personalizado, prefiro ver isso como o comportamento padrão.
Não vejo necessidade de reexecutar constantemente os testes da classe base. Se for uma questão de configuração / desmontagem, pode-se sempre usar @ Before / @After, que será executado novamente todas as vezes antes de uma classe filha da classe base.

Sim, acho que o comportamento pode ser configurável.

Na verdade, não encontro um caso de uso em que desejamos executar o teste pai para cada instância filho.
A menos que você tenha uma dependência direta para testar ( dependsOnMethods ) porque o estado de teste é frequentemente modificado assim nos testes de integração.

@krmahadevan WDYT?

@ vlad230

muito obrigado pela solução detalhada, mas parece um pouco complicado para mim

Seu caso de uso também parece um pouco diferente do normal. Além disso, dei um exemplo prático de como isso pode ser feito. Tudo o que você precisa fazer é apenas adotar e construir sobre isso.

Como o TestNG pode ser personalizado, prefiro ver isso como o comportamento padrão.

Não. Isso não pode ser feito em minha opinião. Os testes devem ser executados o tempo todo. Só porque residem em uma classe base, não significa que devam ser executados seletivamente. TestNG pode ser personalizado. Mas isso não significa que o TestNG precisa oferecer suporte a todas as personalizações. Ele apenas precisa fornecer maneiras pelas quais um usuário possa personalizá-lo de acordo com suas necessidades e trabalhar com ele. O exemplo que compartilhei é um exemplo de aplicação dessas personalizações sem que o TestNG precise passar por alterações.

Não vejo necessidade de reexecutar constantemente os testes da classe base. Se for uma questão de configuração / desmontagem, pode-se sempre usar @ Before / @after, que será executado novamente todas as vezes antes de uma classe filha da classe base.

Para mim, essa é apenas uma maneira de ver as coisas. Se um método específico não deve ser executado todas as vezes, você não deve usar a anotação @Test . Você poderia muito bem usar as anotações de configuração como @BeforeTest (que é executado apenas uma vez por <test> tag) ou @BeforeSuite (que é executado apenas uma vez por <suite> tag) não? Eles não funcionariam para você.

Não tenho certeza se seus métodos de teste em sua classe base são realmente métodos de teste. Para mim, eles soam mais como configurações que estabelecem as condições de configuração a serem satisfeitas para o teste ser executado.

@juherr - Não tenho certeza se concordo com o caso de uso aqui. Um teste, independentemente de onde for encontrado, deve ser executado. Se houver um cenário em que não queremos que ele seja executado para todas as classes filhas, devemos usar as anotações de configuração e não a anotação @Test .

No lado da implementação, acho que isso adicionará muito caos à base de código, especialmente quando as pessoas começarem a recorrer ao uso de grupos como meio de execução.

Além disso, sinto que já existe uma maneira de fazer isso atualmente no TestNG (que compartilhei como exemplo). Por que não aproveitar isso? Este parece ser um caso de uso isolado e não parece se encaixar na maneira normal de como o TestNG é usado.

@krmahadevan , sinto muito, mas acho que você não entendeu o que estou tentando fazer.
Na minha classe pai (BaseTest - por exemplo, DashboardTest), tenho 15 testes agora e três classes filho (por exemplo, DashboardFilterTest, DashboardTreeTest, DashboardEditorTest), cada uma com testes internos. Usando a abordagem que descrevi em meu post, cada vez que executo os testes, esses 15 testes são executados 3 vezes (então, 3x15 = 45 testes sendo executados), não trazendo nenhum valor e estendendo o tempo de execução inutilmente, ao invés de apenas executar o 15 testes uma vez.

Ainda acho que não faz sentido que os testes localizados em uma classe-pai sejam reexecutados sempre que os testes de uma classe-filha são executados. Preferiria que o comportamento padrão do TestNG executasse métodos de teste de uma classe pai uma vez, independentemente do número de classes filho que o estão estendendo.

Se isso não puder ser feito, eu entendo. Obrigado mesmo assim.

@ vlad230

Obrigado por adicionar contexto adicional.

Na minha classe pai (BaseTest - por exemplo, DashboardTest), tenho 15 testes agora e três classes filho (por exemplo, DashboardFilterTest, DashboardTreeTest, DashboardEditorTest), cada uma com testes internos. Usando a abordagem que descrevi em meu post, cada vez que executo os testes, esses 15 testes são executados 3 vezes (então, 3x15 = 45 testes sendo executados), não trazendo nenhum valor e estendendo o tempo de execução inutilmente, ao invés de apenas executar o 15 testes uma vez.

Nesse caso, por que DashboardFilterTest, DashboardTreeTest, DashboardEditorTest estaria estendendo DashboardTest ? Parece que DashboardTest testes precisam residir em sua própria classe em vez de você aproveitar uma abordagem de herança aqui, não? A classe base deve ser refatorada para remover todos os métodos de teste comuns e ser alojada em uma classe de teste separada, e DashboardTest deve ser refatorado para abrigar apenas métodos não @Test comuns não?

Se isso não puder ser feito, eu entendo. Obrigado mesmo assim.

Não é sobre se eles podem ser feitos ou não (os desafios de implementação são um tópico separado), mas ainda estou tendo dificuldade em tentar entender a validade do próprio caso de uso.

@krmahadevan

Nesse caso, por que DashboardFilterTest, DashboardTreeTest, DashboardEditorTest estaria estendendo DashboardTest ? Parece que DashboardTest testes precisam residir em sua própria classe em vez de você aproveitar uma abordagem de herança aqui, não? A classe base deve ser refatorada para remover todos os métodos de teste comuns e ser alojada em uma classe de teste separada, e DashboardTest deve ser refatorado para abrigar apenas métodos não @Test comuns não?

'DashboardTest' contém métodos auxiliares (por exemplo, openDashboard (), createDashboard () etc.) e métodos setup / teardown / cleanup (@ Before / @ After) que também são necessários para as classes filhas. Portanto, não preciso replicar o código nas classes filhas.
Sim, alguns dos testes em 'DashboardTest' podem ser movidos para outras classes específicas, mas eu queria mantê-los aqui, pois são mais gerais (por exemplo, verificar a aparência geral do painel, testes CRUD básicos ou mesmo alguns testes que não funcionaram ' (t cabem em uma categoria específica ou são uma mistura).

Claro, eu poderia ter um não- @ Test classe de teste (embora que seria estranho e alguém poderia adicionar testes aqui por engano), mas seria apenas para evitar este problema, certo?

@ vlad230

'DashboardTest' contém métodos auxiliares (por exemplo, openDashboard (), createDashboard () etc.) e métodos setup / teardown / cleanup (@ Before / @ after) que também são necessários para as classes filhas. Portanto, não preciso replicar o código nas classes filhas.

Isso faz sentido. e sim, eles podem muito bem residir na classe base, de modo que os métodos de configuração também sejam disponibilizados para as classes filhas.

Sim, alguns dos testes em 'DashboardTest' podem ser movidos para outras classes específicas, mas eu queria mantê-los aqui, pois são mais gerais (por exemplo, verificar a aparência geral do painel, testes CRUD básicos ou mesmo alguns testes que não funcionaram ' (t cabem em uma categoria específica ou são uma mistura).

Você ainda pode criar uma classe chamada GenericDashboardTest extends DashboardTest que abriga todos os métodos @Test genéricos nela.

Claro, eu poderia ter uma classe de teste não @ teste (embora seria estranho e alguém pudesse adicionar testes aqui por engano), mas seria apenas para evitar esse problema, certo?

Bem, não é para isso que as revisões de código existem? :) Para evitar tais derrapagens. A classe base (pelo que você explicou) não precisa conter os métodos @Test genéricos comuns. Ele pode muito bem abrigar apenas os auxiliares e as configurações e você pode ter mais uma classe de teste que estende a classe base e abriga os métodos de teste genéricos. Isso deve resolver a confusão em questão.

Encerrando este problema com a resolução _funcionando conforme projetado_

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

blackduck-joe picture blackduck-joe  ·  13Comentários

juherr picture juherr  ·  3Comentários

eskatos picture eskatos  ·  17Comentários

Drimix20 picture Drimix20  ·  18Comentários

juherr picture juherr  ·  8Comentários