Automatizando Testes Funcionais com Selenium + Cucumber

Depois de um bom tempo sem postar, estou de volta. Início de ano é sempre assim, vamos ver se eu mantenho uma regularidade nos posts dessa vez.

Pra começar resolvi escrever sobre Testes Funcionais Automatizados + BDD. Já escrevi sobre os benefícios do BDD em outro post com uma breve overview, mas só teoria. Agora escrevo sobre como configurar um projeto Java do zero utilizando Maven + Cucumber + Selenium.

Como o objetivo é demonstrar um teste funcional automatizado rodando, o exemplo que escolhi para testá-lo foi um simples app que calcula o valor do estacionamento: Parking Calculator.

1) O primeiro passo é criar nosso projeto Maven no eclipse:

File > New > Maven Project > Next > Next > Finish

maven-project

2) Após isso vamos incluir as dependências necessárias:

  • junit 4.12
  • cucumber-java 1.2.0
  • cucumber-junit 1.2.0
  • selenium-java 2.4.4.0
  • selenium-firefox-driver

Abra o pom.xml e inclua utilizando o plugin do Maven para o Eclipse na Aba Dependencies:

dependencias

(Opcional) Incluir plugin do Cucumber no Eclipse:

Com esse plugin podemos visualizar nossas features com formatação, podemos executá-las individualmente a partir do arquivo, bem como abrir o método de teste que implementa um step dentro do nosso arquivo .feature com as user stories criadas. Facilita muito o desenvolvimento.

Help > Install New Software

Update site: http://cucumber.github.com/cucumber-eclipse/update-site

plugin-cucumber

3) Crie uma source folder para os nossos arquivos .feature:

  • src/test/resources

source-folder

4) Crie o package bdd.example para as classes de teste e arquivos .feature:

  • src/test/java
  • src/test/resources

5) No próximo passo iremos definir o cenário de teste da nossa app. Para isso deve ser criado o arquivo parking_calculator.feature dentro de src/test/resources:

Feature: Parking Calculator
	As a customer
	In order to pay my parking tax
	I want to calculate the cost

Scenario: Calculate cost of a day
	Given the customer starts the app
	When the customer chooses 'EP' Lot
	And the customer chooses the Entry date and time
	And the customer chooses the Leaving date and time
	And the customer chooses to calculate
	Then the system must show the total cost $28 of parking
	And the time spent by the customer '(1 Days, 0 Hours, 0 Minutes)'

É aí onde serão definidos as nossas User Stories e o comportamento de nossa App. Aí que está o poder do BDD: Descrever comportamentos em uma linguagem mais alto nível e testar automaticamente para garantir que estamos desenvolvendo corretamente.

Observe que isto possibilita ao desenvolvedor aumentar seu entendimento de negócio da aplicação. Além de favorecer os testes de regressão feitos posteriormente.

No meu cenário Calculate daily rate eu gostaria de calcular a diária para o estacionamento econômico (EP).
O valor da diária definido pelo negócio é $28, logo defino que este valor deve ser o exibido pelo app.

6) Desenvolver a classe de configuração do Cucumber:

@RunWith(Cucumber.class)
@CucumberOptions(plugin = {"pretty", "html:target/test-report",
								"json:target/test-report.json"},
					monochrome = true)
public class CukesRunner {

}

Uma boa vantagem que pude observar em relação ao JBehave é a configuração. A do Cucumber se mostrou bem mais simples, apesar de ter algumas limitações que podemos discutir mais pra frente. Bastar duas annotations com algumas opções e plim! A mágica está feita. 🙂

7) Neste momento nossos testes ainda não estão implementados, podemos executá-los e assim teremos o feedback do Cucumber de que existe cenário de teste ainda não implementado (undefined) e sugere quais steps devem ser implementados:

feature-console

8) Definição e implementação da classe de testes:

public class ParkingCalculatorSteps {

	private static WebDriver browser;

	@Before("@start")
	public void setup() {
		browser = new FirefoxDriver();
		browser.manage().window().setPosition(new Point(200, 100));
	}

	@Given("^the customer starts the app$")
	public void givenTheCustomerStartsTheApp() {
		browser.navigate().to("http://adam.goucher.ca/parkcalc/");
		browser.manage().timeouts().implicitlyWait(1, TimeUnit.SECONDS);
	}

	@When("^the customer chooses '([^\"]*)' Lot$")
	public void whenTheCustomerChoosesTheLot(String lot) {
		WebElement lotSelectbox = browser.findElement(By.id("Lot"));
		List<WebElement> options = lotSelectbox.findElements(By.tagName("option"));
		lotSelectbox.click();

		for (WebElement option : options) {
			if(option.getAttribute("value").equals(lot)) {
				option.click();
			}
		}
	}

	@When("^the customer chooses the Entry date and time$")
	public void whenTheCustomerChoosesEntryDateAndTime() {
		WebElement entryTime = browser.findElement(By.id("EntryTime"));
		WebElement entryDate = browser.findElement(By.id("EntryDate"));
		entryTime.clear();
		entryDate.clear();
		entryTime.sendKeys("13:00");
		entryDate.sendKeys("01/09/2015");
	}

	@When("^the customer chooses the Leaving date and time$")
	public void whenTheCustomerChoosesLeavingDateAndTime() {
		WebElement leavingTime = browser.findElement(By.id("ExitTime"));
		WebElement leavingDate = browser.findElement(By.id("ExitDate"));
		leavingTime.clear();
		leavingDate.clear();
		leavingTime.sendKeys("13:00");
		leavingDate.sendKeys("01/10/2015");
	}

	@When("^the customer chooses to calculate$")
	public void whenTheCustomerChoosesToCalculate() {
		WebElement calculate = browser.findElement(By.name("Submit"));
		calculate.click();
	}

	@Then("^the system must show the total cost \\$(\\d+) of parking$")
	public void thenTheSystemMustShowTheTotalCostOfParking(double cost) {
		String currencyCost = "$ " + cost + "0";
		WebElement result = browser.findElements(By.cssSelector(".SubHead")).get(1);
		Assert.assertEquals(result.getText(), currencyCost);
	}

	@Then("^the time spent by the customer '([^\"]*)'$")
	public void theTimeSpenByTheCustomer(String timeSpent) {
		WebElement result = browser.findElement(By.cssSelector("span.BodyCopy"));
		Assert.assertEquals(result.getText().trim(), timeSpent);
	}

	@After("@finish")
	public void tearDown() {
		browser.quit();
	}

}

Esta classe será responsável por implementar os métodos de testes definidos pelo nosso parking_calculator.feature. O Cucumber parsea cada step definido no nosso cenário pelas palavras-chave Given-When-Then e executa em nossa classe ParkingCalculatorStep o método que casa com a definição dentro das annotations do Cucumber @Given, @When e @Then. Estes testes são executados no Firefox.

Após executar note a velocidade com que o teste é feito. E isso em um simples exemplo.

Conclusão:

Deu pra notar como isso pode facilitar seus testes funcionais em uma aplicação maior? É uma vantagem e ganho de tempo enorme. Some-se isso ao utilizar com um servidor de integração contínua, sua aplicação está bem mais testável, além de mapear pontos mais sensívels a quebra do sistema. Como toda tecnologia tem suas limitações, mas até então pela minha pouca experiência é possível contorná-las desde que o time esteja bem consciente do que deva ser feito.

Utilizei esta metodologia em um projeto durante quase 2 anos e o resultado foi incrível: O projeto já roda em produção, mesmo que ainda de forma interna já é uma boa métrica pra avaliar, e a cada entrega pouquíssimos bugs eram reportados pelo cliente.

Ás vezes é bem caro implementar um teste funcional automatizado, mais até que a própria funcionalidade, cabe ao time definir o momento certo para esta automatização. No começo é normal errar bastante, mas durante o ciclo de desenvolvimento o time vai se sentindo mais confortável e automatizar os testes vai ser normal.

Depois de notar tantos benefícios dentro de um projeto real, pretendo utilizar sempre que possível. 😉

_

Por fim disponibilizo o código-fonte utilizado no exemplo no meu github:

https://github.com/henriqueluz/selenium-cucumber

Espero as discussões. Até a próxima! 😀

Anúncios

6 comentários em “Automatizando Testes Funcionais com Selenium + Cucumber”

      1. Boa tade camarada, achei muito bom seu post, mas acho q esta mais pra ATDD do que pra BDD em si, de qualquer forma foi muito didático, esta de parabéns

  1. Olá. Existe alguma forma de retornar a quantidade de cenários que é colocado nos Examples? Preciso, pois, no After gostaria de executar a limpeza do banco após a execução do teste referente a ultima linha.

    1. Oi Felipe,
      Existe sim, a API do Cucumber Reports te fornece isso.
      Mas uma pergunta rapida: Se voce precisa apenas limpar o banco apos a execucao dos testes, por que precisaria saber o total de cenarios? Isso é possivel te executar ao final dos testes com uma annotation do tipo @AfterClass do JUnit.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s