简体   繁体   English

Spock中的TestNG数据提供程序的类似物

[英]Analog of TestNG data provider in Spock

I'm new to Spock, and currently switching to it, but I inherited plenty of test configuration files which need to be re-used. 我是Spock的新手,目前正在切换到它,但是我继承了许多需要重新使用的测试配置文件。 Each config file is a JSON, having the same name as Spec class. 每个配置文件都是一个JSON,与Spec类的名称相同。 For each test method there is a list of maps with parameters, eg: 对于每种测试方法,都有一个带有参数的地图列表,例如:

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 allowed me to pass test method name in data provider method, so I could return the list of maps depending on test class name and test method name. TestNG允许我在数据提供者方法中传递测试方法名称,因此我可以根据测试类名称和测试方法名称返回映射列表。 I had only one data provider method in my base class: 我的基类中只有一个数据提供程序方法:

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

As a result of this method I get an array of Maps to use in each of test iteration. 这种方法的结果是,我得到了一个Map数组,可以在每个测试迭代中使用。 And then I just specify this method as a DataProvider: 然后,我只是将此方法指定为DataProvider:

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

This works perfectly: declared ones in the base class, it automatically receives the test and provides test data. 这可以完美地工作:在基类中声明了那些,它会自动接收测试并提供测试数据。

The question is: is there a way to apply similar approach in Spock tests? 问题是:是否可以在Spock测试中应用类似的方法?

I'd like to have some getData() method in my base class, where I'm able to read tests parameters depending on the test method name and then pass them into where block. 我想在基类中有一些getData()方法,在这里我可以根据测试方法名称读取测试参数,然后将它们传递到where块中。

I tried to use my json reader as shown below: 我尝试使用JSON阅读器,如下所示:

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

    when:
    ...
    then:
    ...

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

This example gives me required list of maps, but has two problems: 此示例为我提供了所需的地图列表,但有两个问题:

  1. data here - is the full list of maps, not one map for each iteration; 此处的数据 -是地图的完整列表,而不是每次迭代都有一张地图;
  2. I'm forced to explicitly type the name of test method, class etc. 我被迫明确输入测试方法,类等的名称。

Summing up: What is the best way to implement a data provider which will receive test method name and return a list of maps? 总结:实现数据提供程序的最佳方法是什么,该数据提供程序将接收测试方法名称并返回映射列表?

You can solve problem with data using this approach: 您可以使用以下方法解决data问题:

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

It will iterate the list of maps so each test iteration will be parametrized only by that map. 它将迭代映射列表,因此每次测试迭代将仅由该映射参数化。


Current test name can be obtained by: 当前测试名称可以通过以下方式获得:

specificationContext.currentFeature.name

And current iteration name by: 并且当前迭代名称由:

specificationContext.currentIteration.name

But both are not accessible in the where section because it is executed before the test itself where only values from shared context are available. 但是两者都不能在where部分中访问,因为它是在测试本身之前执行的,而只有共享上下文中的值才可用。 So here I'm afraid that you have to enter the test name manually. 因此,在这里恐怕您必须手动输入测试名称。

Update: I found solution how to get feature name in where section for you. 更新:我找到了解决方案,该解决方案如何在您的“ where部分中获取功能名称。 It is realized by an own extension using interceptor. 它是使用拦截器通过自己的扩展实现的。

Feature details container: 功能详细信息容器:

class FeatureDetails {
    String name
}

Extension annotation: 扩展注释:

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 extension with inline interceptor implementation: 具有内联拦截器实现的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()
        })
    }
}

Example usage of the extension: 扩展的用法示例:

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

And the output of above example: 以及上面示例的输出:

DataProviderSpec.test DataProviderSpec.test
[a:1, b:2] [a:1,b:2]
[a:2, b:4] [a:2,b:4]
DataProviderSpec.another test DataProviderSpec。另一个测试
[a:3, b:6] [a:3,b:6]
[a:4, b:8] [a:4,b:8]

First idea was to use only annotated String featureName field in the spec class but there is a problem where visitFeatureAnnotation() method works with different spec instance during each call while loadData() method is executed each time on the first instance. 第一个想法是仅在spec类中使用带注释的String featureName字段,但是存在一个问题,其中在每次调用期间visitFeatureAnnotation()方法在不同的spec实例上工作,而每次在第一个实例上执行loadData()方法时。


Note: You can also add description with values specific to the current iteration using @Unroll annotation. 注意:您也可以使用@Unroll批注添加说明,其中包含特定于当前迭代的值。 For example: 例如:

@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]]
    ...
}

Will produce: 将产生:

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

You could use JsonSlurper . 您可以使用JsonSlurper It basically parses JSON and returns an Object which could be a List or a Map (just cast it). 它基本上解析JSON并返回一个Object,它可以是List或Map(只需将其强制转换)。 You can easily use it in your were block (remember to only use static or @Shared in there). 您可以轻松地在使用它were块(记住,只能使用static@Shared在那里)。

Here is some documentation about JSON in Groovy. 是Groovy中有关JSON的一些文档。

Solved. 解决了。

The following method, declared in the BaseSpec class, gets the name of current spec automatically on a stage of where block and load params from a config file accordingly: BaseSpec类中声明的以下方法,在相应地阻止和加载配置文件中的参数的阶段上where自动获取当前规范的名称:

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

Usage in class, which is a subclass of BaseSpec class: 在类中的用法,它是BaseSpec类的子类:

def "My custom name spec"() {

    when:
    ... 

    then: 
    ...

    where:
    data << getData()
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM