当前位置: 首页 > 知识库问答 >
问题:

模拟Spock中的TestNG数据提供程序

湛骏祥
2023-03-14

我是Spock的新手,目前正在使用它,但我继承了大量需要重复使用的测试配置文件。每个配置文件都是一个JSON,与Spec类同名。对于每种试验方法,都有一个带有参数的地图列表,例如:

LoginSpec.json:
{
  "My first test": [
    {
      "user": "user_one",
      "role": "ADMIN"
    },
    {
      "user": "user_two",
      "role": "REPORTER",
      "other_param": "other"
    }
  ],

  "Some Other Test Method": [
    {
      "url": "/lab1",
      "button_name": "Show news popup"
    }
  ]
}

TestNG允许我在数据提供者方法中传递测试方法名称,因此我可以根据测试类名和测试方法名称返回映射列表。我的基类中只有一个数据提供者方法

public Object[][] getData(String method) {
    DataReader reader = new JsonReader()
    return reader.parse(packageFullName, getClass().simpleName, method)
}

作为这种方法的结果,我得到了一组映射,用于每个测试迭代。然后我将这个方法指定为数据提供者:

@Test(dataProvider = "getData", priority = 1)
void EULA_1(Map data) { <====
    Pages.login.openLoginPage()
    Pages.login.logIn(data.user) <====
    ...
} 

这非常有效:在基类中声明的测试,它会自动接收测试并提供测试数据。

问题是:有没有办法在Spock测试中应用类似的方法?

我想在我的基类中有一些getData()方法,在那里我可以根据测试方法名称读取测试参数,然后将它们传递到哪里块。

我尝试使用json阅读器,如下所示:

def "My first test"() {
    setup:
    println(data)

    when:
    ...
    then:
    ...

    where:
    data = dataReader.parse("JobE2E", "LoginSpec.json", "My first test")
}

本例给出了所需的地图列表,但有两个问题:

  1. 此处的数据-是地图的完整列表,而不是每次迭代的一张地图;
  2. 我被迫显式键入测试方法、类等的名称。

总结:实现将接收测试方法名称并返回映射列表的数据提供程序的最佳方式是什么?

共有3个答案

姬凡
2023-03-14

解决了的。

BaseSpec类中声明的以下方法阶段自动获取当前spec的名称,其中相应地阻止并从配置文件加载参数:

    protected List<Map<String, Object>> getData() {
        String methodName = StackTraceUtils.sanitize(new Throwable()).stackTrace[1].methodName
        FeatureInfo spec = specificationContext.currentSpec.features.find {
            FeatureInfo info ->
                info.dataProviders.any {
                    it.dataProviderMethod.name == methodName
                }
        }
        Class className = getClass()
        String packageFullName = className.package.name
        String packageName = packageFullName[(packageFullName.lastIndexOf(".") + 1)..-1]
        TestDataReader reader = new JsonReader()
        return reader.parse(packageName, className.simpleName, spec.name)
    }

类中的用法,它是BaseSpec类的子类:

def "My custom name spec"() {

    when:
    ... 

    then: 
    ...

    where:
    data << getData()
}
孟茂学
2023-03-14

您可以使用JsonSlurper。它基本上解析JSON并返回一个对象,它可以是List或Map(只需转换它)。您可以在您的was块中轻松使用它(请记住仅在其中使用静态@Shared)。

下面是一些关于Groovy中JSON的文档。

祁俊喆
2023-03-14

您可以使用此方法解决data的问题:

data << dataReader.parse('JobE2E', "${getClass().name}.json", 'My first test')

它将迭代映射列表,因此每个测试迭代将仅由该映射参数化。

当前测试名称可通过以下方式获得:

specificationContext.currentFeature.name

和当前迭代名称:

specificationContext.currentIteration.name

但是在where部分中,这两种方法都是不可访问的,因为它是在测试本身之前执行的,只有来自共享上下文的值可用。 因此,这里恐怕您必须手动输入测试名称。

更新:我为您找到了在where部分获取功能名称的解决方案。它是通过一个使用拦截器的扩展来实现的。

功能详细信息容器:

class FeatureDetails {
    String name
}

扩展注释:

import org.spockframework.runtime.extension.ExtensionAnnotation

import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@ExtensionAnnotation(FeatureDetailsExtension.class)
@interface ShareFeatureDetails {
}

带有内联拦截器实现的Spock扩展:

import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
import org.spockframework.runtime.model.FeatureInfo

class FeatureDetailsExtension extends AbstractAnnotationDrivenExtension<ShareFeatureDetails> {
    def featureDetails = new FeatureDetails()

    @Override
    void visitFeatureAnnotation(ShareFeatureDetails annotation, FeatureInfo feature) {
        feature.addInterceptor({ i ->
            featureDetails.name = feature.name
            feature.spec.allFields.each { f ->
                if (f.type == FeatureDetails.class && f.readValue(i.getInstance()) == null) {
                    f.writeValue(i.getInstance(), featureDetails)
                }
            }
            i.proceed()
        })
    }
}

扩展的示例用法:

class DataProviderSpec extends Specification {
    @Shared
    FeatureDetails currentFeature

    @Unroll("Test #data.a * 2 = #data.b")
    @ShareFeatureDetails
    def 'test'() {
        when:
        println data

        then:
        data.a * 2 == data.b

        where:
        data << loadData()
    }

    @Unroll("Test #data.a * 3 = #data.b")
    @ShareFeatureDetails
    def 'another test'() {
        when:
        println data

        then:
        data.a * 3 == data.b

        where:
        data << loadData()
    }

    def loadData() {
        // this is hard coded example
        println "${getClass().name}.${currentFeature.name}"
        if ('test' == currentFeature.name) return [[a: 1, b: 2], [a: 2, b: 4]]
        if ('another test' == currentFeature.name) return [[a: 3, b: 9], [a: 4, b: 12]]
        return []
        // ... use load from data file (JSON, YAML, XML, ...) instead:
        // return dataReader.parse("${getClass().name}.json", currentFeature.name)
    }
}

和上面例子的输出:

DataProviderSpec。测试[a:1,b:2]
[a:2,b:4]
数据提供者规范。另一项测试[a:3,b:6]
[a:4,b:8]

第一个想法是在spec类中只使用带注释的String FuncureName字段,但存在一个问题,即visitFeatureAnnoection()方法在每次调用期间与不同的spec实例一起工作,而loadData()方法每次都在第一个实例上执行。

注意:您还可以使用@Unroll注释添加特定于当前迭代的描述值。例如:

@Unroll("Test #data.a * 2 = #data.b")
def 'test'() {
    setup:
    ...
    when:
    ...
    then:
    data.a * 2 == data.b

    where:
    data << getData('test')
}

def getData(String methodName) {
    if ('test' == methodName) return [[a: 1, b: 2], [a: 2, b: 4]]
    ...
}

将产生:

测试1*2=2
测试2*2=4

 类似资料:
  • 我已使用以下方法在应用程序中输入凭据。 其中EnterText定义如下: 在测试类中,我写了下面的代码 我得到以下错误: 失败:loginProxy(“11”,“Priya”)java.lang.NumberFormatException:java.lang.Integer处为空。parseInt(未知源)位于java.lang.Integer。parseInt(未知源) 请帮助解决同样的问题。据

  • 我想在类中与数据提供者并行运行测试方法。我需要一个数据提供者,它每次在新测试方法开始为给定的测试运行生成部分动态数据之前都会被调用。让我用伪代码解释一下: 我怎样才能做到这一点?

  • 我有一个TestNG Dataprovider测试,如下所示: 输入csv 读者 测验 有许多输入文件,每个测试都包含一个优先级。我想单独运行优先级为1的测试。 在没有数据提供者的情况下,我可以使用下面的IMethodInterceptor过滤测试。 样品测试: 由于数据提供者的优先级是动态的,因此我无法根据优先级过滤测试。 不应执行其他测试,而应跳过其他测试。

  • 我正在尝试通过TestNG进行多线程测试为测试实例化WebDrivers<代码>@AfterMethod在测试后关闭WebDrivers

  • 错误: 失败:testData org.testng.internal.reflect.MethodMatcherException:数据提供程序不匹配方法:testData([参数{index=0,Type=java.lang.String,DeclaredAnnotations=[]},参数{index=1,Type=java.lang.String,DeclaredAnnotations=[

  • 有人知道TestNG如何根据为测试方法指定的数据提供者名称计算出数据提供者方法是什么吗? 我找到了这个解决方案:https://gist.github.com/ae6rt/3805639 但是,它没有考虑到数据提供者可能是: 在完全不同的类别中定义,或 在父类中定义,并且 该方法可以是静态的,也可以是非静态的 我试图自己破解一些东西,但后来我想我不可能是第一个试图解决这个问题的人,特别是因为显然T