Аналог провайдера данных TestNG в Споке - PullRequest
0 голосов
/ 06 ноября 2018

Я новичок в 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)
}

В результате этого метода я получаю массив карт для использования в каждой итерации теста. И тогда я просто указываю этот метод в качестве DataProvider:

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

Это отлично работает: они объявлены в базовом классе, автоматически получают тест и предоставляют тестовые данные.

Вопрос: есть ли способ применить подобный подход в тестах Спока?

Мне бы хотелось иметь метод getData () в моем базовом классе, где я могу читать параметры тестов в зависимости от имени метода теста и затем передавать их в , где блок.

Я попытался использовать мой JSON Reader, как показано ниже:

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

    when:
    ...
    then:
    ...

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

Этот пример дает мне требуемый список карт, но имеет две проблемы:

  1. data here - полный список карт, а не одна карта для каждой итерации;
  2. Я вынужден явно указать имя метода тестирования, класс и т. Д.

Подводя итог: Каков наилучший способ реализации поставщика данных, который будет получать имя метода тестирования и возвращать список карт?

Ответы [ 3 ]

0 голосов
/ 10 ноября 2018

Вы можете решить проблему с помощью 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 {
}

Расширение спока с реализацией встроенного перехватчика:

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.test
[a: 1, b: 2]
[a: 2, b: 4]
DataProviderSpec.another тест
[a: 3, b: 6]
[a: 4, b: 8]

Первой идеей было использование только аннотированного поля String featureName в классе спецификации, но существует проблема, когда метод visitFeatureAnnotation() работает с различным экземпляром спецификации во время каждого вызова, тогда как метод 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

0 голосов
/ 12 ноября 2018

Решено.

Следующий метод, объявленный в классе 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)
    }

Использование в классе, который является подклассом BaseSpec класса:

def "My custom name spec"() {

    when:
    ... 

    then: 
    ...

    where:
    data << getData()
}
0 голосов
/ 09 ноября 2018

Вы можете использовать JsonSlurper. Он в основном анализирует JSON и возвращает объект, который может быть списком или картой (просто приведите его). Вы можете легко использовать его в своем блоке were (не забудьте использовать там только static или @Shared).

Здесь - некоторая документация о JSON в Groovy.

...