Русский
Русский
English
Статистика
Реклама

Behaviour driven development

Зачем нам вулканец на борту обзор Spock Framework

28.08.2020 16:14:16 | Автор: admin
Автоматизация тестирования помогает постоянно контролировать качество 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.DriverFactoryimport org.openqa.selenium.WebDriverimport spock.lang.Sharedimport spock.lang.Specificationabstract 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.LoginPageimport spock.lang.Issueclass 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 = 3b = Math.random() * 100c = 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.DriverFactoryimport org.openqa.selenium.WebDriverimport spock.lang.Sharedimport spock.lang.Specificationabstract 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 pytestfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom PageObjects.LoginPage import LoginPageclass 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 pytestfrom pytest_bdd import scenario, given, when, thenfrom selenium.webdriver.support import expected_conditionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom 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, несомненно, заслуживает внимания, поскольку подходит для решения сложной распространенной задачи автоматизации бизнес-сценариев для программных продуктов с высокой предметной и организационной сложностью.

Что еще можно почитать:
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru