Разделы портала

Онлайн-тренинги

.
Хороший Gherkin как путь к хорошей автоматизации
27.01.2020 00:00

Автор: Энджи Джонс (Angie Jones)
Оригинал статьи
Перевод: Ольга Алифанова

Разработка на основе поведения, также известная как BDD – это основанная на примерах методология для разработки ПО. Ключевой момент в BDD – это совместная деятельность Бизнеса, Разработки и Тестировщиков. Эти участники также известны как "Три товарища".

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


Давайте посмотрим на пример такой встречи.

Встреча трех товарищей

Команда Parabank создает новую фичу, позволяющую пользователям выводить деньги со счета. Три товарища начинают представлять этот сценарий и поведение приложения.

Они используют синтаксис Gherkin, который позволяет им пользоваться доменно-специфичным языком в формате "Если – Когда – Тогда" (Given, When, Then).

Синтаксис Given-When-Then заставляет их подумать о текущем состоянии системы ("Если"), действии, которое совершено в системе ("Когда"), и результирующем поведении системы ("Тогда").

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

Они размышляют над предусловиями, определяющими текущее состояние системы перед тем, как предприняты какие-либо действия. Они решают, что им нужны:

  • Пользователь, у которого есть учетная запись.
  • Определенное количество денег у этой записи, допустим, $100.

Они записывают это в формате Gherkin, используя два утверждения "Если".

Если у пользователя есть учетная запись

И баланс учетной записи 100.00 долларов

Отметьте использования слова "И". Это ключевое слово используется, если утверждение не единственное. В отличие от "Если А, Если Б", оно записано в более разговорном формате – "Если А и Б".

Теперь, когда предусловия определены, они описывают действие. Это делается при помощи утверждения "Когда".

Когда пользователь выводит 10.00 долларов

И, наконец, команда обсуждает, как система должна себя вести в такой ситуации. Это делается через два "Тогда"-утверждения.

Тогда баланс учетной записи должен стать равным 90,00 долларов

И должна быть записана новая транзакция.

Теперь у команды есть полный описанный сценарий, и три товарища знают, что им нужно создать.

Преимущества BDD

У использования BDD много преимуществ. Члены команды могут сотрудничать и добиваться общего понимания еще до того, как началась разработка. Это значит, что все двусмысленности и разницы во мнениях можно обсудить очень рано и разобраться с ними до старта разработки.

Все говорят на одном доменно-специфичном языке, поэтому нет терминологической путаницы.

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

Использование Gherkin для автоматизации тестов

Сценарий в стиле Gherkin можно добавлять в фича-файл. Вот пример фича-файла, который служит как входные данные для сценариев тест-автоматизации.

Однако многие команды застревают на этом этапе, потому что им не хватает множества деталей выполнения этого сценария.

Он гласит, что у пользователя есть учетная запись. У какого пользователя? Где информация об учетной записи?

Он гласит, что пользователь выводит десять долларов, но не говорит о шагах, которые для этого нужно предпринять. На какие страницы мне перейти, и на какие элементы интерфейса кликнуть?

Он описывает ожидаемый результат, но не говорит, куда идти, чтобы их проверить.

Из-за этого тестировщики зачастую переделывают краткий фича-файл во что-то вроде этого:

Да, тут куда больше деталей, но у этого подхода есть минусы.

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

Одна из причин, по которой три товарища создают сценарии на основе поведения, а не реализации – это то, что на самом деле то, как вы это сделаете, не так уж важно. Добавление детализации ведет к утере цели поведения в сценарии.

Если мы не даем деталей реализации для разработки продукта – мы не должны давать их и для разработки автотестов.

Вернемся к краткому сценарию, описывающему поведение. Да, там отсутствует множество деталей, но это нормально. На самом деле это отлично!

Как мы узнаем, как это внедрять, спросите вы?

Мы оставим это на усмотрение того, кто пишет код автоматизации. Мы верим, что разработчики создадут фичу, даже если у них нет детальных подробностей – и мы делаем то же самое для разработки кода автотестов.

Давайте возьмем каждый из этих шагов и посмотрим, как превратить их в связующий код, который выполняет этот сценарий, в то же время следуя принципам хорошей тест-автоматизации.

Создание (связующего) кода автотестов

Используя Cucumber, тест-автоматизатор берет сценарии, созданные тремя товарищами, и кладет их в исполняемый фича-файл. Вот с каким файлом он будет работать:

Feature: Withdraw
    As a customer, I want to withdraw funds from my account
 
Scenario: Withdraw from account when funds are available
    Given a customer has an account
    And the account balance is 100.00 dollars
    When the customer withdraws 10.00 dollars
    Then the account balance should be 90.00 dollars
    And a new transaction should be recorded

Тест-автоматизатор создает Java-файл для кода автоматизации. Код в этом файле называется определением шагов, и он соответствует шагам фича-файла.

package cucumber.stepdefs;
 
public class WithdrawStepDefs {
 
}
 

Он также добавляет файл прогона, чтобы сообщить Cucumber, где находятся фича-файлы и файлы определения шагов.

package cucumber;
 
import org.junit.runner.RunWith;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
 
@RunWith(Cucumber.class)
@CucumberOptions(
  plugin = {"pretty"},
  glue = {"cucumber.stepdefs"},
  features = {"src/test/java/cucumber/features"})
public class CucumberTestOptions {
 
}
 

Шаг 1

Теперь все готово для того, чтобы привязать шаги из фича-файлы к связующему коду! Вот первый шаг:

Given a customer has an account
 

К нему добавляется связующий код в WithdrawStepDefs, привязывающийся к шагу. Это делается с использованием аннотации @Given с текстом из фича-файла.

package cucumber.stepdefs;
 
public class WithdrawStepDefs {
 
  @Given("a customer has an account")
  public void createNewAccount(){
 
  }
}
 

Сразу за этим аннотированным связующим шагом идет метод, который должен выполняться, когда вызывается шаг. Этот метод можно назвать как угодно, и он может выполнять любой код на усмотрение тест-инженера.

Снова напомним, что инженер не знает, что это за пользователь и что у него за учетная запись. Это нормально. Он может выбрать пользователя для сценария, и вывести детали об этом пользователе на более низкий уровень, а не хранить их внутри фича-файла. Инженер решает добавить информацию о пользователе в файл свойств. У него уже есть один файл свойств, где хранится URL приложения, а также место расположения исполняемого chromedriver, и он просто добавляет новую секцию с деталями пользователя.

################
#  APP         #
################

app.url=http://parabank.parasoft.com/parabank/

################
#  SELENIUM    #
################

webdriver.chrome.driver=resources/chromedriver

################
#  CUSTOMER    #
################

customer.id=12212
customer.username=john
customer.password=demo

Чтобы сделать что-то с этой учетной записью, пользователю нужно авторизоваться. Это тоже не то, что стоит хранить в фича-файле, потому что это нужно практически для всех сценариев! Выводим на уровень ниже.

Тест-инженер решает положить это в отдельный Java-файл, чтобы внедрить это только один раз, а затем тест-сценарии автоматически выполнят это через наследование:

package base;
 
import components.LoginComponent;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
 
import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;
 
public class BaseTests {
 
    protected static WebDriver driver;
 
    @BeforeClass
    public static void launchApplication() {
        loadTestProperties();
 
        driver = new ChromeDriver();
        driver.get(System.getProperty("app.url"));
 
        new LoginComponent(driver).login();
    }
 
    @AfterClass
    public static void closeBrowser() {
        driver.quit();
    }
 
    private static void loadTestProperties(){
        Properties props = System.getProperties();
        try {
            props.load(
                    new FileInputStream(new File("resources/test.properties")));
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
}
 

Автоматизатор также обновляет файл определения шагов, чтобы расширить класс BaseTests.

public class WithdrawStepDefs extends BaseTests
 

Итак, с авторизацией разобрались. Теперь надо разобраться с учетной записью. Хорошо, что детали учетной записи не были описаны в фича-файле. Это позволяет тест-инженеру следовать хорошим практикам. Он не хочет использовать запись, которая уже существует, потому что если в параллели запускаются еще какие-то тесты, пользующиеся этой учеткой (и ожидающие определенного количества средств), это вызовет противоречия в тестовых данных, и тесты упадут. Поэтому он решает создать новую запись на лету, а так как в фича-файлей нет деталей внедрения – он может это сделать.

Другое принятое решение – использовать веб-сервисы приложения для создания такой записи. Создание записи – не часть теста, это его предусловие. Нет никаких причин делать это на уровне интерфейса – это займет много времени. Так как эти шаги не были продиктованы фича-файлом, инженер может выбрать наилучший способ внедрения. Он использует инструмент Rest-Assured для вызова веб-сервиса и парсинга ответа.

Примечание: исходный код используемых файлов можно найти на Github по ссылке в конце статьи.

package cucumber.stepdefs;
 
import base.BaseTests;
import cucumber.api.java.en.*;
import io.restassured.response.Response;
import services.Endpoints;
import utils.ServicesUtils;
 
import static java.lang.String.*;
import static utils.ServicesUtils.HttpMethod.*;
import static io.restassured.path.json.JsonPath.from;
 
public class WithdrawStepDefs extends BaseTests {
 
    private Response response;
    private int accountId;
 
    @Given("a customer has an account")
    public void createNewAccount(){
        String customerId = System.getProperty("customer.id");
        String endpoint = format(Endpoints.CREATE_ACCOUNT, customerId);
        response = ServicesUtils.execute(endpoint, POST);
        accountId = from(response.asString()).get("id");
    }
}
 

Это покрывает первый шаг сценария.

Шаг 2

Перейдем к следующему шагу

And the account balance is 100.00 dollars
 

Теперь автоматизатору надо убедиться, что у новой учетной записи есть баланс в 100 долларов. Он решает сделать это на уровне служб, чтобы тесты были быстрее.

    @And("^the account balance is (.*) dollars$")
    public void setAccountBalance(float desiredBalance) {
        float currentBalance = getCurrentBalance();
        if(desiredBalance != currentBalance){
            deposit(desiredBalance - currentBalance);
        }
    }
 
    private float getCurrentBalance(){
        String endpoint = format(Endpoints.GET_ACCOUNT, accountId);
        response = ServicesUtils.execute(endpoint, GET);
        return from(response.asString()).get("balance");
    }
 
    private void deposit(float amount){
        ServicesUtils.execute(format(Endpoints.DEPOSIT, accountId, amount), POST);
    }
 

Заметьте, что в первой строке для суммы используется (*). Это делает шаг переиспользуемым для будущих сценариев. Из-за этого метод должен принимать параметр в качестве значения – это переменная desiredBalance. Смотрите, как эффективен наш инженер, когда точные шаги не продиктованы фича-файлом!

Шаг 3

Следующий шаг тоже можно не выполнять через UI! Слава богу, что шаг говорит только о том, что должно произойти, а не о том, как это должно произойти. Автоматизатор решает вновь воспользоваться вызовом веб-службы.

When the customer withdraws 10.00 dollars
 
    @When("the customer withdraws (.*) dollars")
    public void withdraw(double withdrawAmount){
        String endpoint = format(Endpoints.WITHDRAW, accountId, withdrawAmount);
        response = ServicesUtils.execute(endpoint, POST);
    }
 

Шаг 4

Что дальше?

Then the account balance should be 90.00 dollars
 

Утверждение "Когда". Настало время проверок. Автоматизатору нужно убедиться, что теперь на счету 90 долларов. Еще один вызов веб-службы спешит на помощь!

    @Then("the account balance should be (.*) dollars")
    public void verifyBalance(float balance){
        Assert.assertEquals(balance, getCurrentBalance(), 0.0f);
    }
 

Шаг 5.

Последний шаг. Еще одна проверка.

And a new transaction should be recorded
 

Автоматизатор размышляет, как это сделать. Он может сделать это через вызов веб-службы, но знает, что важно в том числе проверить, что запись о транзакции правильно отображается пользователю, и все детали находятся на своих местах.

Вот как выглядит интерфейс, когда все работает:


На этой странице КУЧА деталей. Автоматизатор хочет убедиться, что все тут верно: общий баланс, доступный баланс, номер учетной записи, ее тип, записи о транзакции, и так далее.

Он не рвется писать все эти проверки. И затем его осеняет – ему и не нужно их писать! Все это не определялось фича-файлом, и инженер может принять мудрое решение и воспользоваться более эффективным подходом. Он решает использовать Applitools Eyes API для единичной визуальной проверки скриншота всей страницы целиком. Таким образом все, что нужно, будет покрыто, а кода будет меньше.

    @Then("a new transaction should be recorded")
    public void verifyTransactionRecord(){
        AccountActivityPage activityPage = NavigationUtils.goToAccountActivity(driver, accountId);
        Assert.assertEquals(valueOf(accountId), activityPage.getAccountId());
 
        eyes.open(driver, "Parabank", "Withdraw from account when funds are available");
        eyes.checkWindow();
        eyes.close();
    }
 

Так как часть данных на странице будет меняться после каждого прогона теста (номер учетной записи и даты транзакций), она просто аннотирует эти области в дашборде Applitools, чтобы визуальная проверка учитывала динамичность этих областей.

Хороший Gherkin, хорошая автоматизация

Так как инженер-автоматизатор взял файл Gherkin от встречи трех товарищей и не правил его, добавляя детали реализации, он смог работать умнее, когда создавал код теста. Он мог при необходимости пользоваться интерфейсом, API, вызовами базы данных, и чем угодно еще для создания более эффективных тестов, в то же время следуя принципам хорошей тест-автоматизации.

Полное решение на GitHub

Обсудить в форуме