Testng: 来自超类的@Test 方法为每个子类重新执行

创建于 2019-06-10  ·  12评论  ·  资料来源: cbeust/testng

TestNG 版本 6.14.3

预期行为

无论有多少子类继承了超类,超类中的@Test方法都会执行一次。

实际行为

@Test方法与每个子类一起执行。

该问题是否可以在 runner 上重现?

  • [ ] 贝壳
  • [ ] 马文
  • [x] 摇篮
  • [ ] 蚂蚁
  • [ ] 日蚀
  • [ ] 智能
  • [ ] 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");
    }
}

我正在从如下所示的 TestNG xml 文件并行运行这些测试:

<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测试?

@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>仅执行一次)

我不确定您的基类中的测试方法是否真的是测试方法。 对我来说,它们听起来更像是建立要满足测试运行的设置条件的配置。

@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() 等)和 setup/teardown/cleanup(@Before/@After)方法。 所以,我不需要在子类中复制代码。
是的,“DashboardTest”中的一些测试可以移到其他一些特定的类,但我想将它们保留在这里,因为它们更通用(例如检查仪表板的整体外观、基本的 CRUD 测试甚至一些没有的测试) t 适合特定类别或混合)。

当然,我可以有一个非@Test 测试类(虽然这会很奇怪,有人可能会错误地在这里添加测试),但这只是为了避免这个问题,对吧?

@vlad230

'DashboardTest' 包含子类所需的辅助方法(例如 openDashboard()、createDashboard() 等)和 setup/teardown/cleanup(@Before/@after)方法。 所以,我不需要在子类中复制代码。

这是有道理的。 是的,它们可以很好地驻留在基类中,以便子类也可以使用配置方法。

是的,“DashboardTest”中的一些测试可以移到其他一些特定的类,但我想将它们保留在这里,因为它们更通用(例如检查仪表板的整体外观、基本的 CRUD 测试甚至一些没有的测试) t 适合特定类别或混合)。

您仍然可以创建一个名为GenericDashboardTest extends DashboardTest ,其中包含所有通用的@Test方法。

当然,我可以有一个非@test 测试类(虽然这很奇怪,有人可能会错误地在这里添加测试),但这只是为了避免这个问题,对吧?

好吧,这不是代码审查的目的吗:) 防止这种滑点。 基类(根据您的解释)不需要包含通用的通用@Test方法。 它可以很好地仅容纳帮助程序和配置,您还可以拥有一个扩展基类并容纳通用测试方法的测试类。 这应该可以解决手头的困惑。

以 _working as design_ 的分辨率关闭此问题

此页面是否有帮助?
0 / 5 - 0 等级