TestNG 以编程方式分配 DataProvider

TestNG assign programatically DataProvider

如果我有方法:

@Test(dataProvider = "webTarget")
void testFirst(WebTarget target) {
    // ...
}

我可以在 TestNG 中创建一个监听器或其他东西吗,如果我有一个方法:

@Test
void testFirst(WebTarget target) {
    // ...
}

然后它会自动注入特定的 dataProvider,而无需明确指定它 @Test(dataProvider = "webTarget") ?

如果事先知道每种测试方法的数据提供者方法,则可以使用 IAnnotationTransformer。但是您需要 运行 使用 xml 套件文件进行测试。

注意: Map.of (doc) 是在java 9 中引入的。如果您使用的是以前的版本,那么您可以使用普通哈希映射创建 dpNameMap

public class CustomAnnotationTransformer implements IAnnotationTransformer {
    private static final Map<String, String> dpNameMap = Map.of("methodOne", "dataOne",
                                                                "methodTwo", "dataTwo",
                                                                "methodThree", "dataThree");

    @Override
    public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method method) {
        if(method != null && dpNameMap.containsKey(method.getName())) {
            annotation.setDataProvider(dpNameMap.get(method.getName()));
        }
    }

}

同时在您的套件文件中添加以下内容:

<listeners>
    <listener class-name="com.yourpackage.CustomAnnotationTransformer" />
</listeners>

理想情况下,最简单的方法是:

  1. 定义一个抽象 class 在其中定义所需的数据提供者以及数据提供者将提供的数据作为其来源并将其提供给测试方法(可能类似于数据提供者从注入的值中获取数据)
  2. 进行测试 classes,扩展此抽象 class,然后在 org.testng.IAnnotationTransformer 实现中,您只需将数据提供程序方法名称注入测试 class.

如果您不想也使用抽象 class,那么这里有另一种选择。这种看起来像是一种迂回的方式。

对于这个例子,我使用的依赖注入框架是 Guice。

我们将在此示例中使用的接口如下

/**
 * Lets any test class expose the injected values to any caller.
 */
public interface ObjectGetter {

  /**
   * @return - The {@link Student} object that is required.
   */
  Student getStudent();
}
/**
 * Allows for setting the actual object to be used by any data provider
 */
public interface ObjectSetter {

  /**
   * @param student - The {@link Student} object
   */
  void setStudent(Student student);
}

我们在此示例中使用的 Guice 模块如下所示

import com.google.inject.Binder;
import com.google.inject.Module;

public class MyLocalGuiceModule implements Module {

  @Override
  public void configure(Binder binder) {
    binder.bind(Student.class).toInstance(new Student(100, "KungFu-Panda"));
  }
}

这是测试 class 的样子

import com.google.inject.Inject;

import static org.assertj.core.api.Assertions.assertThat;

import org.testng.annotations.Guice;
import org.testng.annotations.Test;

@Guice(modules = MyLocalGuiceModule.class)
public class SampleTestClass implements ObjectGetter {

  @Inject
  private Student student;

  @Override
  public Student getStudent() {
    return student;
  }

  @Test
  public void testMethod(Student s) {
    String text = s.toString();
    assertThat(text).isEqualTo("Student{id=100, name='KungFu-Panda'}");
  }
}

这是单独的数据提供程序 class 的样子

import org.testng.annotations.DataProvider;

public class DataProviderHouse implements ObjectSetter {

  private Student student;

  @DataProvider(name = "students")
  public Object[][] getStudents() {
    return new Object[][]
        {
            {student}
        };
  }

  @Override
  public void setStudent(Student student) {
    this.student = student;
  }
}

注释转换器如下所示:

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.testng.IAnnotationTransformer;
import org.testng.annotations.ITestAnnotation;

public class LocalTransformer implements IAnnotationTransformer {

  @Override
  public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor,
      Method testMethod) {
    annotation.setDataProviderClass(DataProviderHouse.class);
    annotation.setDataProvider("students");
  }
}

数据提供程序侦听器如下所示:

import org.testng.IDataProviderListener;
import org.testng.IDataProviderMethod;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;

public class DataProviderListener implements IDataProviderListener {

  @Override
  public void beforeDataProviderExecution(IDataProviderMethod dataProviderMethod,
      ITestNGMethod method, ITestContext iTestContext) {
    Object dpInstance = dataProviderMethod.getInstance();
    if (!(dpInstance instanceof ObjectSetter)) {
      return;
    }
    Object testInstance = method.getInstance();
    if (!(testInstance instanceof ObjectGetter)) {
      return;
    }
    ((ObjectSetter) dpInstance).setStudent(((ObjectGetter) testInstance).getStudent());
  }
}

这是套房 xml 的样子

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="dynamic_data_provider_suite" verbose="2">
  <listeners>
    <listener class-name="com.rationaleemotions.dynamic.LocalTransformer"/>
    <listener class-name="com.rationaleemotions.dynamic.DataProviderListener"/>
  </listeners>
  <test name="dynamic_data_provider_test" verbose="2">
    <classes>
      <class name="com.rationaleemotions.dynamic.SampleTestClass"/>
    </classes>
  </test>
</suite>

这是预计会发生的事件链:

  1. 测试 class 使用 Guice 模块将所需的依赖项注入测试 class。
  2. 测试 class 通过接口 com.rationaleemotions.dynamic.ObjectGetter
  3. 将注入的依赖项暴露给任何调用者(在本例中为数据提供者侦听器)
  4. 我们有一个 org.testng.IAnnotationTransformer 的实现,使用它我们将数据提供程序 class 和方法引用注入到测试方法中。
  5. 数据提供程序 class 是一个单独的 class,它实现了 com.rationaleemotions.dynamic.ObjectSetter,使用它可以获取应该用于数据驱动测试的数据。
  6. 我们创建了 org.testng.IDataProviderListener 的实现,TestNG 提供该实现以窃听数据提供者调用前后的事件。使用这个侦听器,我们从测试中提取出 Guice 注入的数据 class,然后将其注入回数据提供者所属的对象。

这样做有点长,但更能使数据提供者真正动态化。

您的使用里程可能会有所不同,具体取决于您希望采用这种“复杂但复杂的方法”的实际用例。