HimeraSearchDB
Carding_EbayThief
triada
CrackerTuch
d-shop

НОВОСТИ Зачем нам вулканец на борту: обзор Spock Framework

NewsBot
Оффлайн

NewsBot

.
.
Регистрация
21.07.20
Сообщения
40.408
Реакции
1
Репутация
0
Автоматизация тестирования помогает постоянно контролировать качество IT-продукта, а также снижать затраты в долгосрочной перспективе. В автоматизации существуют различные подходы, например, Behavior Driven Development (BDD), разработка через поведение.

С этим подходом связаны инструменты cucumber, robot framework, behave и другие, в которых разделены сценарии выполнения и реализация каждой конструкции. Такое разделение помогает составить удобочитаемые сценарии, но требует значительных затрат времени и поэтому может быть непрактичным при написании реализации.

Рассмотрим, как можно упростить работу с BDD, используя подходящие инструменты – например, фреймворк Spock, который сочетает в себе красоту, удобство принципов BDD и особенности jUnit.

zeppk61fpfexomq_alzsbphrz-o.png


Spock framework


Spock – фреймворк для тестирования и спецификации приложений на языках Java и Groovy. Благодаря использованию в качестве основы платформы JUnit этот фреймворк совместим со всеми популярными IDE (в частности, IntelliJ IDEA), различными инструментами сборки (Ant, Gradle, Maven) и continuous integration (CI) серверами.

Как разработчики фреймворка, Spock «вдохновлен , , , , , , и другими увлекательными формами жизни».

В этой статье мы рассмотрим последнюю доступную версию, Spock Framework 2.0. Ее особенности: возможность использования JUnit5, Java 8+, groovy 2.5 (также существует сборка с версией 3.0). Spock распространяется по лицензии Apache 2.0 и имеет отзывчивое сообщество пользователей. Разработчики фреймворка продолжают дорабатывать и развивать Spock, который уже включает в себя множество расширений, позволяющих тщательно настроить запуск тестов. Например, одно из наиболее интересных анонсированных направлений доработки – это добавление параллельного исполнения тестов.

Groovy


Groovy является объектно-ориентированным языком программирования, разработанным для платформы Java как дополнение с возможностями Python, Ruby и Smalltalk. Groovy использует Java-подобный синтаксис с динамической компиляцией в JVM байт-код и напрямую работает с другим Java-кодом и библиотеками. Язык может использоваться в любом Java-проекте или как скриптовый язык.

К особенностям groovy относятся: как статическая, так и динамическая типизация; встроенный синтаксис для списков, массивов и регулярных выражений; перегрузка операций. При этом замыкания в Groovy появились задолго до Java.

Groovy хорошо подходит для быстрой разработки тестов, когда есть возможность применять «синтаксический сахар», схожий с python, и не задумываться о типизации объектов.

Особенности Spock Framework


Одна из ключевых особенностей фреймворка – у разработчика есть возможность писать спецификации с ожидаемыми с использованием принципов BDD подхода. Этот подход позволяет составлять бизнес-ориентированные функциональные тесты для программных продуктов с высокой предметной и организационной сложностью.

Спецификация представляет собой класс groovy, расширяющий spock.lang.Specification


class MyFirstSpecification extends Specification {
// fields
// fixture methods
// feature methods
// helper methods
}

Спецификация может содержать различные вспомогательные поля, которые инициируются для каждого класса спецификации.

С помощью аннотации @Shared можно дать доступ к полю классам-наследникам спецификации.


abstract class PagesBaseSpec extends Specification {

@Shared
protected WebDriver driver


def setup() {
this.driver = DriverFactory.createDriver()
driver.get(" ")
}

void cleanup() {
driver.quit()
}

}


Методы настройки класса спецификации:


def setupSpec() {} // запускается при работе первого feature метода из спецификации
def setup() {} // запускается перед каждым feature методом
def cleanup() {} // запускается после каждого feature метода
def cleanupSpec() {} // запускается после работы последнего feature метода из спецификации

В следующей таблице рассмотрим, у каких ключевых слов и методов Spock framework есть аналоги в JUnit.

tfuqnovhlf_g_ccziuydcaerohw.png


Блоки теста


В Spock Framework каждая фаза теста выделена в отдельный блок кода (см. пример в ).

fyrlo4b9utzo9a_od2wi4pwv-d0.png


Блок кода начинается с лейбла и завершается началом следующего блока кода или окончанием теста.

Блок given отвечает за настройку начальных условий теста.

Блоки when, then всегда используются вместе. В блоке when – стимулятор, раздражитель системы, а в блоке then – ответная реакция системы.

В тех случаях, когда есть возможность сократить конструкцию when-then до одного выражения, можно использовать один блок expect. Далее будут использованы примеры из официальной документации Spock framework:


when:
def x = Math.max(1, 2)

then:
x == 2


или одно выражение


expect:
Math.max(1, 2) == 2

Блок cleanup применяют для освобождения ресурсов перед следующей итерацией теста.


given:
def file = new File("/some/path")
file.createNewFile()

// ...

cleanup:
file.delete()


Блок where применяют для передачи данных для тестирования (Data Driven Testing).


def "computing the maximum of two numbers"() {
expect:
Math.max(a, b) == c

where:
a code>

Виды передачи входных данных будут рассмотрены далее.

Пример реализации теста на Spock Framework


Далее рассмотрим подходы к реализации тестирования веб-страницы авторизации пользователя в системе с использованием selenium.


import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification

abstract class PagesBaseSpec extends Specification {

@Shared
protected WebDriver driver

def setup() {
this.driver = DriverFactory.createDriver()
driver.get(" ")
}

void cleanup() {
driver.quit()
}
}


Здесь мы видим базовый класс спецификации страницы. В начале класса мы видим импорт необходимых классов. Далее представлена аннотация shared, позволяющая классам-наследникам получить доступ к веб-драйверу. В блоке setup() мы видим код инициализации веб-драйвера и открытия веб-страницы. В блоке cleanup() – код завершения работы веб-драйвера.

Далее перейдем к обзору спецификации страницы авторизации пользователя.


import pages.LoginPage
import spock.lang.Issue

class LoginPageTest extends PagesBaseSpec {

@Issue("QAA-1")
def "QAA-1: Authorization with correct login and password"() {

given: "Login page"
def loginPage = new LoginPage(driver)

and: "Correct login and password"
def adminLogin = "adminLogin"
def adminPassword = "adminPassword"

when: "Log in with correct login and password"
loginPage.login(adminLogin, adminPassword)

then: "Authorized and moved to main page"
driver.currentUrl == " "
}
}


Спецификация страницы авторизации наследуется от базовой спецификации страниц. Аннотация Issue задает идентификатор теста во внешней системе трекинга (например, Jira). В следующей строке мы видим название теста, которое по соглашению задается строковыми литералами, что позволяет использовать любые символы в названии теста (в том числе и русскоязычные). В блоке given происходит инициализация page object класса страницы авторизации, а также получение корректных логина и пароля для авторизации в системе. В блоке when выполняется действие по авторизации. В блоке then – проверка ожидаемого действия, а именно – успешная авторизация и переадресация на главную страницу системы.

На примере данной спецификации мы видим наиболее значимый плюс использования парадигмы BDD в spock – спецификация системы одновременно является и ее документацией. Каждый тест описывает определенное поведение, каждый шаг в тесте имеет свое описание, понятное не только разработчикам, но и заказчикам. Описание блоков может быть представлено не только в исходном коде теста, но и в диагностических сообщениях или отчетах о работе теста.

В фреймворке предусмотрена возможность передавать различные логины и пароли для тестирования (параметризировать тест).

Data Driven Testing в Spock Framework


Data Driven Testing = table-driven testing = parameterized testing​

Для тестирования сценария с несколькими параметрами можно использовать различные варианты их передачи.

Таблицы данных (Data Tables)


Рассмотрим несколько примеров из официальной документации фреймворка.


class MathSpec extends Specification {
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c

where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
}


Каждая строка в таблице – отдельная итерация теста. Также таблица может быть представлена и одним столбцом.


where:
a | _
1 | _
7 | _
0 | _

_ — объект-заглушка класса спецификации.

Для лучшего визуального восприятия параметров можно переписать пример выше в следующем виде:


def "maximum of two numbers"() {
expect:
Math.max(a, b) == c

where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
}

Теперь мы видим, что a, b – входные параметры, а c – ожидаемое значение.

Потоки данных (Data pipes)


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


...
where:
a code>

Здесь левый сдвиг
Для каждой итерации теста будут запрашиваться следующие данные из списка для каждой переменной:

1 итерация: a=1, b=3, c=3;
2 итерация: a=7, b=4, c=7;
3 итерация: a=0, b=0, c=0.

Причем входные данные могут не только передаваться явно, но и запрашиваться при необходимости из различных источников. Например, из базы данных:


@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")

def "maximum of two numbers"() {
expect:
Math.max(a, b) == c

where:
[a, b, c] code>


Переменная как данные (Data Variable Assignment)



...
where:
a = 3
b = Math.random() * 100
c = a > b ? a : b

Здесь мы видим динамически вычисляемую переменную c в тестовых данных.

Комбинация различных видов передачи параметров



...
where:
a | _
3 | _
7 | _
0 | _

b b ? a : b

Никто не запрещает вам применять сразу несколько видов передачи, если это необходимо.

Пример реализации параметризованного теста на Spock Framework



@Issue("QAA-1-parametrized")
def "QAA-1-parametrized: Authorization with correct login and password"() {

given: "Login page"
def loginPage = new LoginPage(driver)

when: "Log in with correct login and password"
loginPage.login(login, password)

then: "Authorized and moved to main page"
driver.currentUrl ==" "

where: "Check for different logins and passwords"
login | password
"adminLogin" | "adminPassword"
"moderatorLogin" | "moderatorPassword"
"userLogin" | "userPassword"
}

Здесь мы видим уже знакомый нам блок where, в котором заданы ключи параметров (логинов и паролей), которые хранятся в файле конфигурации.

Ввиду особенностей используемой реализации спецификации, цикл настройки веб-драйвера и его закрытие (а значит и закрытие браузера) будет выполняться для каждого параметра, что негативно скажется на времени выполнения теста. Предлагаем доработать спецификацию и улучшить время работы теста.

Пример реализации параметризованного теста с доработанной спецификацией


До доработки



abstract class PagesBaseSpec extends Specification {

@Shared
protected WebDriver driver


def setup() {
this.driver = DriverFactory.createDriver()
driver.get(" ")
}

void cleanup() {
driver.quit()
}

}


После доработки



import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification

abstract class PagesNoRestartBaseSpec extends Specification {

@Shared
protected WebDriver driver

def setupSpec() {
this.driver = DriverFactory.createDriver()
}

def setup() {
this.driver.get(" ")
}

def cleanup() {
this.driver.get(" ")
this.driver.manage().deleteAllCookies();
}

void cleanupSpec() {
this.driver.quit()
}
}

В обновленной спецификации мы видим, что процедура создания веб-драйвера будет выполняться только при настройке класса спецификации, а закрытие браузера – только после завершения работы тестов из спецификации. В методе setup() мы видим тот же код получения веб-адреса сервиса и его открытие в браузере, а в методе cleanup() – переход по адресу для завершения работы с сервисом у текущего пользователя и удаления файлов куки (для тестирования текущего веб-сервиса данной процедуры достаточно, чтобы имитировать «уникальный» запуск). Код самого теста не изменился.

В итоге с помощью нехитрых доработок мы получили минимум двукратное уменьшение времени работы автотеста, по сравнению с первичной реализацией.

Сравнение тестов на testNG, pytest, pytest-bdd


Для начала мы рассмотрим реализацию теста на тестовом фреймворке testNG на языке программирования Java, который также, как и Spock Framework, вдохновлен фреймворком jUnit и поддерживает data-driven testing.


package javaTests;

import org.testng.Assert;
import org.testng.annotations.*;
import pages.LoginPage;


public class LoginPageTest extends BaseTest {


@BeforeClass
public final void setup() {
createDriver();
driver.get(" ");
}

@DataProvider(name = "userParameters")
public final Object[][] getUserData(){
return new Object[][] {
{"adminLogin", "adminPassword"},
{"moderatorLogin", "moderatorPassword"},
{"userLogin", "userPassword"}
};
}

@Test(description = "QAA-1-1: Authorization with correct login and password",
dataProvider = "userParameters")
public final void authorizationWithCorrectLoginAndPassword(String login, String password){
//Login page
LoginPage loginPage = new LoginPage(driver);

//Log in with correct login and password
loginPage.login(login, password);

//Authorized and moved to main page
Assert.assertEquals(" ", driver.getCurrentUrl());
}

@AfterMethod
public final void cleanup() {
driver.get(" ");
driver.manage().deleteAllCookies();
}

@AfterClass
public final void tearDown() {
driver.quit();
}
}

Здесь мы можем видеть тестовый класс со всеми необходимыми setup(), cleanup() методами, а также параметризацию теста в виде дополнительного метода getUserData() с аннотацией @DataProvider, что выглядит несколько громоздко, после того, что мы рассмотрели в тесте с использованием Spock Framework. Также для понимания того, что происходит в тесте, были оставлены комментарии, аналогичные описанию шагов.

Стоит отметить, что в testNG, в отличие от Spock Framework, реализована поддержка параллельного выполнения теста.

9kisitc0zzhaqpsp4hqvaswdej0.png


Далее перейдем к тесту с использованием тестового фреймворка pytest на языке программирования Python.


import pytest
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from PageObjects.LoginPage import LoginPage


class TestLogin(object):

@pytest.mark.parametrize("login,password", [
pytest.param(("adminLogin", "adminPassword"), id='admin'),
pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
pytest.param(("userLogin", "userPassword"), id='user')
])
def test_authorization_with_correct_login_and_password(self, login, password, driver, test_cleanup):
# Login page
login_page = LoginPage(driver)
# Log in with correct login and password
login_page.login(login, password)

# Authorized and moved to main page
assert expected_conditions.url_to_be(" ")

@pytest.fixture()
def test_cleanup(self, driver):
yield "test"
driver.get(" ")
driver.delete_all_cookies()


Здесь мы также видим поддержку data-driven testing в виде отдельной конструкции, схожей с @DataProvider в testNG. Метод настройки веб-драйвера «спрятан» в фикстуре driver. Благодаря динамической типизации и фикстурам pytest, код этого теста выглядит чище, чем на Java.

8b6xsdyibize1c1o3_0-l17x3cy.png


Далее перейдем к обзору кода теста с использованием плагина pytest-bdd, который позволяет писать тесты в виде feature файлов Gherkin (чистый BDD-подход).

login.feature


Feature: Login page
A authorization

Scenario: Authorizations with different users
Given Login page
When Log in with correct login and password
Then Authorized and moved to main page



test_login.py


import pytest
from pytest_bdd import scenario, given, when, then
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from PageObjects.LoginPage import LoginPage


@pytest.mark.parametrize("login,password", [
pytest.param(("adminLogin", "adminPassword"), id='admin'),
pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
pytest.param(("userLogin", "userPassword"), id='user')
])
@scenario('login.feature', 'Authorizations with different users')
def test_login(login, password):
pass


@given('Login page')
def login_page(driver):
return LoginPage(driver)


@when('Log in with correct login and password')
def login_with_correct_login_and_password(login_page, login, password):
login_page_object = login_page
login_page_object.login(login, password)


@then('Authorized and moved to main page')
def authorized_and_moved_to_main_page(driver, login):
assert expected_conditions.url_to_be(" ")

Из плюсов можно выделить то, что это все еще фреймворк pytest, который имеет множество плагинов для различных ситуаций, в том числе и для параллельного запуска тестов. Из минусов – сам чистый BDD-подход, который будет постоянно ограничивать разработчика своими особенностями. Spock Framework дает возможность писать более лаконичный и простой в оформлении код, по сравнению со связкой PyTest + pytest-bdd.

4_3bqiltnbvnppljxxrnzzb0bzw.png


Заключение


В этой статье мы рассмотрели возможность упрощения работы с BDD с помощью фреймворка Spock. Подводя итоги, кратко выделим основные, на наш взгляд, плюсы и минусы Spock по сравнению с некоторыми другими распространенными тестовыми фреймворками.

Плюсы:

  • Использование принципов BDD вместо чистого BDD-подхода дает большую гибкость при написании тестов.
  • Написанная тестовая спецификация является также и документацией системы.
  • Наличие различных расширений для настройки тестов.
  • Язык groovy (динамическая типизация, синтаксический сахар, closures или замыкания).


Минусы:

  • Динамическая типизация языка groovy. Поскольку применяется динамическая типизация, то механизмы предугадывания, используемые в IDE для анализа содержимого переменной, при долгой работе могут начать сбоить. Если рассматривать Intellij IDEA, то постоянно ведутся доработки в этом направлении, что, несомненно, радует.
  • Динамическая компиляция groovy кода в JVM байт-код. Если кратко, то не стоит писать все подряд на groovy, поскольку вы можете существенно проиграть во времени компиляции данного кода, особенно если он занимает много строк кода и часто используется. Важные части своего тестового фреймворка все же стоит писать на java, а groovy оставить для тестов.
  • Набор расширений не такой обширный, как у testNG, к примеру. Как следствие – отсутствие параллельного запуска тестов. Есть планы добавить эту функциональность, но сроки их реализации неизвестны.

В конечном итоге, инструмент Spock Framework, несомненно, заслуживает внимания, поскольку подходит для решения сложной распространенной задачи автоматизации бизнес-сценариев для программных продуктов с высокой предметной и организационной сложностью.

Что еще можно почитать:
 
Сверху Снизу