Associados a essa abordagem estão pepino, estrutura de robô, comportamento e outros, que separam os scripts de execução e a implementação de cada construção. Essa separação ajuda a escrever scripts legíveis, mas é demorada e, portanto, pode ser impraticável ao escrever uma implementação.
Vejamos como você pode simplificar sua experiência com o BDD usando as ferramentas certas - por exemplo, a estrutura Spock, que combina a beleza, a conveniência dos princípios do BDD e os recursos do jUnit.

Estrutura Spock
Spock é uma estrutura de teste e especificação para aplicativos Java e Groovy. Usando a plataforma JUnit como base, esta estrutura é compatível com todos os IDEs populares (em particular, IntelliJ IDEA), várias ferramentas de construção (Ant, Gradle, Maven) e servidores de integração contínua (CI).
Conforme escrevem os desenvolvedores da estrutura, Spock "é inspirado por JUnit , RSpec , jMock , Mockito , Groovy , Scala , Vulcans e outras formas de vida fascinantes."
Neste artigo, daremos uma olhada na versão mais recente disponível, Spock Framework 2.0. Suas características: a capacidade de usar JUnit5, Java 8+, groovy 2.5 (também existe um assembly com a versão 3.0). Spock é licenciado pelo Apache 2.0 e tem uma comunidade de usuários responsiva. Os desenvolvedores da estrutura continuam a refinar e desenvolver o Spock, que já inclui muitas extensões que permitem ajustar a execução do teste. Por exemplo, uma das áreas de melhoria mais interessantes anunciadas é a adição de execução de teste paralela.
Groovy
Groovy é uma linguagem de programação orientada a objetos desenvolvida para a plataforma Java como um complemento com recursos Python, Ruby e Smalltalk. O Groovy usa uma sintaxe semelhante a Java com compilação dinâmica para bytecode JVM e funciona diretamente com outro código Java e bibliotecas. A linguagem pode ser usada em qualquer projeto Java ou como uma linguagem de script.
Os recursos do groovy incluem: digitação estática e dinâmica; sintaxe embutida para listas, arrays e expressões regulares; sobrecarregando operações. No entanto, os encerramentos no Groovy apareceram muito antes do Java.
O Groovy é adequado para o desenvolvimento de teste rápido, onde você pode usar o açúcar sintático do tipo python sem ter que se preocupar com a digitação de objetos.
Recursos do Spock Framework
Um dos principais recursos da estrutura é que o desenvolvedor tem a capacidade de escrever especificações com as características esperadas do sistema usando os princípios da abordagem BDD. Essa abordagem possibilita a composição de testes funcionais voltados para o negócio para produtos de software de alta complexidade organizacional e temática.
A especificação é uma classe bacana que estende spock.lang.Specification
class MyFirstSpecification extends Specification {
// fields
// fixture methods
// feature methods
// helper methods
}
O BOM pode conter vários campos auxiliares que são acionados para cada classe de BOM.
Usando a anotação @Shared, você pode dar acesso ao campo para classes herdadas da especificação.
abstract class PagesBaseSpec extends Specification {
@Shared
protected WebDriver driver
def setup() {
this.driver = DriverFactory.createDriver()
driver.get("www.anywebservice.ru")
}
void cleanup() {
driver.quit()
}
}
Métodos de personalização da classe BOM:
def setupSpec() {} // feature
def setup() {} // feature
def cleanup() {} // feature
def cleanupSpec() {} // feature
A tabela a seguir mostra quais palavras-chave e métodos na estrutura Spock têm contrapartes JUnit.

Blocos de massa
No Spock Framework, cada fase de teste é separada em um bloco de código separado (consulte a documentação para obter um exemplo ).

Um bloco de código começa com um rótulo e termina com o início do próximo bloco de código ou o final de um teste.
O bloco fornecido é responsável por definir as condições de teste iniciais.
Bloqueia o quando , o então sempre usado junto. O bloco when contém o estimulante, estímulo do sistema, e o bloco then contém a resposta do sistema.
Nos casos em que é possível encurtar a cláusula when-then para uma única expressão, você pode usar um único bloco expect... Os exemplos a seguir serão usados da documentação oficial do framework Spock:
when:
def x = Math.max(1, 2)
then:
x == 2
ou uma expressão
expect:
Math.max(1, 2) == 2
O bloco de limpeza é usado para liberar recursos antes da próxima iteração do teste.
given:
def file = new File("/some/path")
file.createNewFile()
// ...
cleanup:
file.delete()
A cláusula where é usada para transferir dados para teste (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]
}
Os tipos de transferência de dados de entrada serão discutidos abaixo.
Exemplo de implementação de teste no Spock Framework
A seguir, consideraremos abordagens para a implementação de teste da página da web para autorização do usuário no sistema usando selênio.
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()
}
}
Aqui vemos a classe base da especificação da página. No início da aula, vemos a importação das classes necessárias. A seguir está a anotação compartilhada , que permite que classes derivadas acessem o driver da web. No bloco setup () , vemos o código para inicializar o driver da web e abrir a página da web. O bloco cleanup () contém o código para encerrar o driver da web.
A seguir, vamos passar para uma visão geral da especificação da página de login do usuário.
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"
}
}
A especificação da página de autorização herda da especificação da página base. A anotação de problema especifica o ID do teste em um sistema de rastreamento externo (como Jira). Na próxima linha, vemos o nome do teste, que por convenção é definido por strings literais, o que permite que você use quaisquer caracteres no nome do teste (incluindo os russos). No bloco dado , o objeto de página da classe de página de autorização é inicializado, e o login e senha corretos para autorização no sistema são obtidos. No bloco quando , a ação de autorização é executada. O bloco then é utilizado para verificar a ação esperada, ou seja, autorização bem-sucedida e redirecionamento para a página principal do sistema.
No exemplo desta especificação, vemos a vantagem mais significativa de usar o paradigma BDD no spock - a especificação do sistema é ao mesmo tempo sua documentação. Cada teste descreve um determinado comportamento, cada etapa do teste tem sua própria descrição, que é compreensível não apenas para desenvolvedores, mas também para clientes. As descrições dos blocos podem ser apresentadas não apenas no código-fonte do teste, mas também em mensagens de diagnóstico ou relatórios sobre a operação do teste.
A estrutura fornece a capacidade de transferir vários logins e senhas para teste (parametrizar o teste).
Teste orientado a dados no Spock Framework
Teste baseado em dados = teste baseado em tabela = teste parametrizado
Para testar um cenário com vários parâmetros, você pode usar várias opções para transmiti-los.
Tabelas de dados
Vejamos alguns exemplos da documentação oficial do framework.
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
}
}
Cada linha da tabela é uma iteração de teste separada. Além disso, a tabela pode ser representada por uma coluna.
where:
a | _
1 | _
7 | _
0 | _
_ É um objeto stub da classe BOM.
Para uma melhor percepção visual dos parâmetros, você pode reescrever o exemplo acima da seguinte forma:
def "maximum of two numbers"() {
expect:
Math.max(a, b) == c
where:
a | b || c
1 | 3 || 3
7 | 4 || 7
0 | 0 || 0
}
Agora podemos ver que a , b são entradas e c é o valor esperado.
Canais de dados
Em alguns casos, usar a tabela de projeto será muito complicado. Nesses casos, você pode usar o seguinte tipo de passagem de parâmetro:
...
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]
Aqui, o deslocamento para a esquerda << é um operador groovy sobrecarregado que agora serve para adicionar itens à lista.
Para cada iteração de teste, os seguintes dados serão solicitados da lista para cada variável:
1 iteração: a = 1, b = 3, c = 3;
2ª iteração: a = 7, b = 4, c = 7;
3 iteração: a = 0, b = 0, c = 0.
Além disso, os dados de entrada podem não apenas ser transmitidos explicitamente, mas também solicitados, se necessário, de várias fontes. Por exemplo, do banco de dados:
@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")
}
Atribuição de Variável de Dados
...
where:
a = 3
b = Math.random() * 100
c = a > b ? a : b
Aqui vemos a variável c calculada dinamicamente nos dados de teste.
Combinação de diferentes tipos de transferência de parâmetros
...
where:
a | _
3 | _
7 | _
0 | _
b << [5, 0, 0]
c = a > b ? a : b
Ninguém o proíbe de usar vários tipos de transferência ao mesmo tempo, se necessário.
Um exemplo de implementação de um teste parametrizado no 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"
}
Aqui vemos o já conhecido bloco where, no qual são definidas as chaves dos parâmetros (logins e senhas), que ficam armazenados no arquivo de configuração.
Devido às peculiaridades da implementação da especificação utilizada, o ciclo de configuração do driver web e seu fechamento (e consequentemente fechamento do navegador) serão realizados para cada parâmetro, o que afetará negativamente o tempo de execução do teste. Sugerimos finalizar a especificação e melhorar o tempo de execução do teste.
Um exemplo da implementação de um teste parametrizado com uma especificação modificada
Antes da revisão
abstract class PagesBaseSpec extends Specification {
@Shared
protected WebDriver driver
def setup() {
this.driver = DriverFactory.createDriver()
driver.get("www.anywebservice.ru")
}
void cleanup() {
driver.quit()
}
}
Após revisão
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()
}
}
Na especificação atualizada, vemos que o procedimento para a criação de um driver da web será executado apenas ao configurar a classe de especificação e fechar o navegador somente após a execução dos testes da especificação. No método setup (), vemos o mesmo código para obter o endereço da web do serviço e abri-lo em um navegador, e no método cleanup (), vamos para www.anywebservice.ru/logout para terminar de trabalhar com o serviço para o usuário atual e excluir os cookies (para testar o serviço da web atual, este procedimento é suficiente para simular um lançamento "exclusivo"). O código de teste em si não mudou.
Como resultado, com a ajuda de melhorias simples, conseguimos uma redução de pelo menos duas vezes no tempo de operação do autoteste, em comparação com a implementação inicial.
Comparação de testes para testNG, pytest, pytest-bdd
Primeiro, veremos a implementação de um teste no framework de teste testNG na linguagem de programação Java, que, como o Spock Framework, é inspirado no framework jUnit e oferece suporte a testes orientados a dados.
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();
}
}
Aqui podemos ver a classe de teste com todos os métodos setup (), cleanup () necessários, bem como a parametrização do teste na forma de um método getUserData () adicional com a anotação @DataProvider, que parece um pouco complicado depois do que examinamos no teste usando Spock Estrutura. Além disso, para entender o que está acontecendo no teste, foram deixados comentários semelhantes à descrição das etapas.
É importante notar que testNG, ao contrário do Spock Framework, oferece suporte à execução de testes paralelos.

A seguir, vamos passar para um teste usando a estrutura de teste pytest na linguagem de programação 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()
Aqui também vemos o suporte para testes orientados a dados como uma construção separada, semelhante a @DataProvider em testNG. O método para configurar o driver da web está "escondido" no dispositivo do driver. Graças à tipagem dinâmica e dispositivos pytest, o código para este teste parece mais limpo do que Java.

A seguir, vamos passar para uma visão geral do código de teste usando o plug-in pytest-bdd, que permite escrever testes na forma de arquivos de recursos do Gherkin (abordagem pura do 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")
Uma das vantagens é que ainda é um framework pytest, que possui diversos plug-ins para diversas situações, inclusive para execução de testes em paralelo. A desvantagem é a abordagem pura do BDD, que limitará constantemente o desenvolvedor com suas próprias características. O Spock Framework torna possível escrever um código mais conciso e fácil de projetar em comparação com o pacote PyTest + pytest-bdd.

Conclusão
Neste artigo, vimos como simplificar o trabalho com o BDD usando a estrutura Spock. Resumindo, vamos destacar brevemente os principais, em nossa opinião, os prós e contras de Spock em comparação com alguns outros frameworks de teste comuns.
Prós:
- Usar os princípios do BDD em vez de uma abordagem pura do BDD oferece mais flexibilidade ao escrever testes.
- A especificação do teste escrito também é a documentação do sistema.
- .
- groovy ( , , closures ).
:
- groovy. , , IDE , . Intellij IDEA, , , , .
- groovy JVM -. , groovy, , . java, groovy .
- O conjunto de extensões não é tão extenso quanto o do testNG, por exemplo. Como resultado, não há execução paralela de testes. Existem planos para adicionar essa funcionalidade, mas o momento de sua implementação é desconhecido.
Em última análise, o Spock Framework sem dúvida merece atenção, pois é adequado para resolver o complexo problema comum de automatizar cenários de negócios para produtos de software com alta complexidade organizacional e temática.
O que mais você pode ler: