BDD: Escrever um conjunto de testes automatizado não é ciência de foguetes

Andy Macdonald Blocked Desbloquear Seguir Seguindo 29 de dezembro de 2018

Se você estiver procurando por um modelo simples para criar sua própria estrutura de automação de teste para um aplicativo da Web usando Java, Selenium / Selenide e Cucumber – você pode encontrar um aqui .

Esta é uma breve visão geral do BDD com detalhes sobre a implementação de um conjunto de testes automatizado usando Java, Selenium e Pepino.

Ao tentar alavancar o BDD em um projeto, as pessoas frequentemente se atolam com as etapas necessárias para construir um conjunto de testes automatizado, como se fosse um empreendimento gigantesco – não é .

Espero que este artigo irá desmistificar o processo e fornecer algumas dicas úteis para você começar com o seu projeto.

O que diabos é BDD?

O BDD (Behavior Driven Development) é um conjunto de práticas de desenvolvimento que visa reduzir a sobrecarga causada por dores de cabeça dev comuns – como ter que revisitar o trabalho porque os requisitos foram mal compreendidos ou não foram completamente elaborados.

BDD é uma extensão do TDD com as únicas diferenças reais nas metodologias sendo a forma como os casos de teste no BDD são especificados.

O BDD usa linguagem clara e onipresente para casos de teste que são derivados "de fora para dentro", ou seja, diretamente dos requisitos de negócios e dos resultados de negócios desejados para o comportamento do aplicativo.

A linguagem não ambígua usada nesses casos de teste é geralmente escrita na forma de Gherkin DSL (linguagem específica do domínio).

Um recurso escrito em maxixe

Então você teve uma idéia para um aplicativo matador – você vai revolucionar o mundo com a tela de login perfeita !

Vamos começar escrevendo como queremos que isso se comporte no Gherkin.

(Observação: as etapas de segundo plano são uma forma abreviada de criar um conjunto de etapas para cada cenário)

 Funcionalidade: Página de login para aplicativo da web 

Fundo:
Dado que
eu navego para o aplicativo da web e não estou logado
 Cenário: página de login exibida 
Então eu vejo uma página de " login "
E a página de login tem um formulário de login
E há um logotipo na página de login
 Cenário: Eu tento fazer o login com um usuário inválido 
Quando eu faço login com um usuário inválido
Em seguida , vejo uma mensagem de erro " Nome de usuário ou senha está incorreto "

Cenário: Eu tento fazer o login com um usuário válido
Quando eu faço login com um usuário válido
Então eu vejo uma página "em casa "

A vantagem real de escrever casos de teste nessa forma é que eles servem a dois propósitos:

  • eles servem como documentação para o seu projeto que qualquer um pode ler e entender.
  • eles claramente articulam o que precisamos fazer com nosso código.

Definições de Etapa

As definições de etapa mapeiam as etapas definidas no Gherkin (o arquivo de recurso) para ações concretas. Nós concordamos em como queremos que nosso aplicativo se comporte e documente, agora precisamos escrever um código que teste esse comportamento.

Usando o Cucumber, podemos escrever definições de etapas em branco no seguinte formato – em que anotações e expressões regulares mapeiam etapas em um arquivo de recurso para ações concretas:

 ... 
 @Given ("^ eu navego para o aplicativo da web e não estou logado em $") 
public void iBrowseToTheWebAppAndIAmNotLoggedIn ()
{
// FAÇAM
}

@ Quando ("^ faço login com um usuário inválido $")
public void iLoginWithAnInvalidUser ()
{
// FAÇAM
}

@ Then ("^ eu vejo um " ([^ "] *) " mensagem de erro $ ")
public void iSeeAErrorMessage (String errorMessage)
{
// FAÇAM
}
 ... 

(Nota: Muitos IDEs possuem plugins que gerarão automaticamente para você a partir de arquivos de recursos)

Selênio / Selenide para Automação de Navegador

Queremos ser capazes de executar nossos testes em um aplicativo da Web em execução real que vamos escrever. Para fazer isso, podemos combinar nossas definições de etapa do pepino com os recursos da estrutura do Selenium .

O Selenium é uma estrutura de automação de navegador, você pode aproveitar um WebDriver e interagir com uma página da Web usando um navegador real e selecionando elementos na tela usando seletores CSS ou XPath . Existem muitas implementações diferentes do WebDriver – para vários navegadores diferentes, bem como implementações 'headless', nas quais um navegador é executado sem uma GUI.

 classe final pública WebDriverUtils 
{

private static WebDriver Webdriver;

public Web sincronizado WebDriver getWebDriverInstance ()
{
if ( webDriver == nulo)
{
DriverPathLoader. loadDriverPaths (nulo);
webDriver = novo ChromeDriver ();
}
return webDriver ;
}
 } 

Exemplo de declaração de um elemento está em uma página usando o padrão Selenium:

 Assert.assertNotNull(webDriver.findElement(By.cssSelector("#username")).getText()); 

Por si só, o Selenium é incrivelmente poderoso, mas pode ter algumas desvantagens – por exemplo, a verbosidade de seletores para selecionar elementos em uma página, lidar com exceções e como o framework lida com temporização e timeouts.

Exemplo de declaração de um elemento está em uma página usando Selenide:

É aqui que o Selenide entra. É essencialmente um invólucro muito limpo e fácil de usar em torno do framework Selenium.

 $ ("#username"). deveBe ( visível ); 

Você concorda que o acima é consideravelmente menos detalhado e muito mais legível.

Modelo de Objeto de Página

O modelo de objeto de página é um padrão de design em que páginas e elementos em uma página são representados como classes orientadas a objetos – quando você precisa interagir com um elemento na tela, não chama o WebDriver diretamente – em vez disso, chama os métodos em uma classe representando o elemento na página.

Vamos pegar o exemplo para nosso aplicativo de página de login:

 classe pública LoginPage estende AbstractPage 
{

string final estática privada PAGE_NAME = "login";

LoginPage (String basePath)
{
super (basePath);
}

@Sobrepor
public String getPageName ()
{
devolver PAGE_NAME ;
}

@Sobrepor
public void go ()
{
URI loginUri = URI. create (basePath) .resolve (getPageName ());
aberto (loginUri.toString ());
}

public LoginFormElement loginForm ()
{
return new LoginFormElement ();
}
}

A classe acima representa nossa página de login – ela retorna o elemento principal que existe na página ( LoginFormElement ) como uma classe separada que possui seus próprios métodos e responsabilidades, os quais ela manipula (como o login).

 classe pública LoginFormElement estende AbstractElement <LoginFormElement> 
{
private static final String INPUT_USERNAME = "#username";
final estático privado String INPUT_PASSWORD = "#password";
final estático privado String SUBMIT = "#login_submit";

@Sobrepor
public LoginFormElement isVisible ()
{
$ ( INPUT_USERNAME ) .shouldBe ( visível );
$ ( INPUT_PASSWORD ) .shouldBe ( visível );
$ (Enviar) .shouldBe (visível);
devolva isto;
}

login void público (nome de usuário String, senha String)
{
$ ( INPUT_USERNAME ) .setValue (username);
$ ( INPUT_PASSWORD ) .setValue (senha);
$ (Enviar) .click ();
}
}

Você verá que os seletores de elemento CSS foram definidos aqui para campos de entrada de nome de usuário e senha, bem como para um botão de envio de login.

Quando construímos o aplicativo da Web, precisamos apenas certificar que rotulamos os campos e o botão com esses identificadores e, se tudo estiver bem, os testes serão aprovados!

Colocando tudo junto

Aqui está o que reunimos para as definições das etapas para testar nossa página de login com base nos cenários que descrevemos no arquivo de recurso.

É quase certo que refatoremos isso (por exemplo, precisaremos descobrir como injetar ou saber as credenciais válidas para nosso cenário de "login com um usuário válido"), mas isso nos dá uma base sobre a qual basear-se.

 classe pública LoginStepDefinition estende BaseStepDefinition 
{

private LoginPage loginPage = pages.loginPage ();
AlertElement privado AlertElement = pages.alertComponent ();
private LoginFormElement loginFormElement = loginPage.loginForm ();

@Given ("^ eu navego para o aplicativo da web e não estou logado em $")
public void iBrowseToTheWebAppAndIAmNotLoggedIn ()
{
loginPage.go ();
}


@ E ("^ a página de login tem um formulário de login $")
public void theLoginPageHasALoginForm ()
{
loginPage.loginForm (). isVisible ();
}

@ Quando ("^ faço login com um usuário inválido $")
public void iLoginWithAnInvalidUser ()
{
loginFormElement.login ("invalid", "invalid");
}

@ Then ("^ eu vejo um " ([^ "] *) " mensagem de erro $ ")
public void iSeeAErrorMessage (String errorMessage)
{
alertElement.isVisible (). hasMessage (errorMessage);
}

@ Quando ("^ faço login com um usuário válido $")
public void iLoginWithAValidUser ()
{
loginFormElement.login ("válido", "válido");
}

@ E ("^ existe um logotipo na página de login $")
public void thereIsALogoOnTheFrontPage ()
{
loginPage.logo (). isVisible ();
}
}

Depois de mais algum trabalho escrevendo definições de etapas e escrevendo objetos de página, temos um conjunto de testes automatizado que falhará de forma confiável para nosso aplicativo de página de login (porque ainda não construímos nada para passar nos testes).

Conforme você cria seu aplicativo e adiciona mais funcionalidade, mais testes passam. Para garantir que os testes sejam aprovados, você só precisa criar a funcionalidade apropriada com os seletores apropriados (conforme definido em seus modelos e elementos de objetos de página).

À medida que mais requisitos são adicionados, você adiciona mais testes. Simples.

Eu sugiro puxar para baixo o projeto e entender como o framework se encaixa e tentar executá-lo (e vê-lo falhar).

Leitura Adicional

Notas

O exemplo foi descrito usando Java, Pepino, Selenium e Selenide – definitivamente não é a única maneira de escrever um conjunto de testes automatizado . Toda linguagem tem seu próprio conjunto de frameworks, mas as abordagens subjacentes são geralmente as mesmas.

Não há nenhuma razão para que você não possa usar uma linguagem totalmente diferente para seus testes automatizados em vez da linguagem em que o projeto foi escrito – desde que não seja muito exótico (não escreva em FORTRAN ou em assembly!), não é totalmente contra a estratégia de tecnologia do seu projeto e da empresa e há habilidades bem integradas em sua equipe para dar suporte ao idioma.

Na verdade, eu recomendo isso se o membro da equipe responsável pelo conjunto de testes automatizado tiver habilidades mais desenvolvidas em diferentes estruturas ou idiomas. Descubra o que funciona melhor para o seu projeto, considerando as capacidades de sua equipe.

Obrigado pela leitura! ?

Texto original em inglês.