[Тестирование IT-систем, Программирование, Тестирование веб-сервисов, Управление разработкой] Зачем нам вулканец на борту: обзор Spock Framework

Автор Сообщение
news_bot ®

Стаж: 6 лет 4 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
28-Авг-2020 18:36

Автоматизация тестирования помогает постоянно контролировать качество IT-продукта, а также снижать затраты в долгосрочной перспективе. В автоматизации существуют различные подходы, например, Behavior Driven Development (BDD), разработка через поведение.
С этим подходом связаны инструменты cucumber, robot framework, behave и другие, в которых разделены сценарии выполнения и реализация каждой конструкции. Такое разделение помогает составить удобочитаемые сценарии, но требует значительных затрат времени и поэтому может быть непрактичным при написании реализации.
Рассмотрим, как можно упростить работу с BDD, используя подходящие инструменты – например, фреймворк Spock, который сочетает в себе красоту, удобство принципов BDD и особенности jUnit.

Spock framework
Spock – фреймворк для тестирования и спецификации приложений на языках Java и Groovy. Благодаря использованию в качестве основы платформы JUnit этот фреймворк совместим со всеми популярными IDE (в частности, IntelliJ IDEA), различными инструментами сборки (Ant, Gradle, Maven) и continuous integration (CI) серверами.
Как пишут разработчики фреймворка, Spock «вдохновлен JUnit, RSpec, jMock, Mockito, Groovy, Scala, вулканцами и другими увлекательными формами жизни».
В этой статье мы рассмотрим последнюю доступную версию, 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("www.anywebservice.ru")
    }
    void cleanup() {
        driver.quit()
    }
}

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

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

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

Блок кода начинается с лейбла и завершается началом следующего блока кода или окончанием теста.
Блок 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 << [5, 3]
  b << [1, 9]
  c << [5, 9]
}

Виды передачи входных данных будут рассмотрены далее.
Пример реализации теста на 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("www.anywebservice.ru")
    }
    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 == "www.anywebservice.ru/main"
    }
}

Спецификация страницы авторизации наследуется от базовой спецификации страниц. Аннотация 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 << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]

Здесь левый сдвиг << – перегруженный groovy оператор, который теперь выполняет роль добавления элементов в список.
Для каждой итерации теста будут запрашиваться следующие данные из списка для каждой переменной:
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] << sql.rows("select a, b, c from maxdata")
}

Переменная как данные (Data Variable Assignment)
...
where:
a = 3
b = Math.random() * 100
c = a > b ? a : b

Здесь мы видим динамически вычисляемую переменную c в тестовых данных.
Комбинация различных видов передачи параметров
...
where:
a | _
3 | _
7 | _
0 | _
b << [5, 0, 0]
c = a > 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 =="www.anywebservice.ru/main"
   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("www.anywebservice.ru")
    }
    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("www.anywebservice.ru")
    }
    def cleanup() {
        this.driver.get("www.anywebservice.ru/logout")
        this.driver.manage().deleteAllCookies();
    }
    void cleanupSpec() {
        this.driver.quit()
    }
}

В обновленной спецификации мы видим, что процедура создания веб-драйвера будет выполняться только при настройке класса спецификации, а закрытие браузера – только после завершения работы тестов из спецификации. В методе setup() мы видим тот же код получения веб-адреса сервиса и его открытие в браузере, а в методе cleanup() – переход по адресу www.anywebservice.ru/logout для завершения работы с сервисом у текущего пользователя и удаления файлов куки (для тестирования текущего веб-сервиса данной процедуры достаточно, чтобы имитировать «уникальный» запуск). Код самого теста не изменился.
В итоге с помощью нехитрых доработок мы получили минимум двукратное уменьшение времени работы автотеста, по сравнению с первичной реализацией.
Сравнение тестов на 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("www.anywebservice.ru");
    }
    @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("www.anywebservice.ru/main", driver.getCurrentUrl());
    }
    @AfterMethod
    public final void cleanup() {
        driver.get("www.anywebservice.ru/logout");
        driver.manage().deleteAllCookies();
    }
    @AfterClass
    public final void tearDown() {
        driver.quit();
    }
}

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

Далее перейдем к тесту с использованием тестового фреймворка 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("www.anywebservice.ru/main")
    @pytest.fixture()
    def test_cleanup(self, driver):
        yield "test"
        driver.get("www.anywebservice.ru/logout")
        driver.delete_all_cookies()

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

Далее перейдем к обзору кода теста с использованием плагина 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("www.anywebservice.ru/main")

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

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

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

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

===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_testirovanie_itsistem (Тестирование IT-систем), #_programmirovanie (Программирование), #_testirovanie_vebservisov (Тестирование веб-сервисов), #_upravlenie_razrabotkoj (Управление разработкой), #_avtomatizatsija_testirovanija (автоматизация тестирования), #_sdet, #_behaviour_driven_development, #_bdd, #_spock_framework, #_testing, #_spock, #_testirovanie (тестирование), #_avtotesty (автотесты), #_blog_kompanii_simbirsoft (
Блог компании SimbirSoft
)
, #_testirovanie_itsistem (
Тестирование IT-систем
)
, #_programmirovanie (
Программирование
)
, #_testirovanie_vebservisov (
Тестирование веб-сервисов
)
, #_upravlenie_razrabotkoj (
Управление разработкой
)
Профиль  ЛС 
Показать сообщения:     

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

Текущее время: 04-Июл 09:17
Часовой пояс: UTC + 5