Blog > PHP Testing (Parte 2): Automação de testes em BDD com Codeception

03/dez

Muitos frameworks open-source em PHP permitem automatizar testes nas camadas de unidade, API e Interface Gráfica. O Codeception possui todos estes atributos e a escrita é feita usando uma DSL muito poderosa e simples de utilizar, veremos alguns conceitos de automação de testes e como executar os primeiros testes usando Codeception.

BDD: Desenvolvimento Dirigido por Comportamentos

Enquanto parte das empresas de tecnologia (talvez os 20% da regra de Paretto) usam documentação seguindo padrões de UML (Casos de Uso, Diagrama de Sequências, de Classes, etc) outras reportam as necessidades dos usuários através de chamados, e-mails, cartas, ou mesmo escrita em papel de pão. Este modelo faz com que a compreensão das necessidades seja muito difícil, por não serem claras ou completas o suficiente para servir de base para a construção de um software, vamos ver um exemplo:

Olá José, aqui é a Maria.
Preciso que altere nosso sistema.
Quero ter a possibilidade de registrar as movimentações de entrada
e saída (no máximo 100 reais) de nosso caixa.

O conceito de BDD, visa descrever um software baseado em seus comportamentos, utilizando cenários que descrevem como o software deve funcionar, facilitando assim a compreensão e criação do software, vejamos um exemplo:

Funcionalidade: Gerenciar Movimentações

Sendo o administrador de uma loja 
Posso gerenciar movimentações
Para que possa ver, de forma simples, as entradas e saídas de valores

Cenário: Gerando uma movimentação de entrada
Dado que eu esteja autenticado como administrador
E que eu tenha acessado o menu "Movimentações"
E clicado em "Nova movimentação” para inserir uma movimentação
Quando eu informar os dados:
|tipo|valor|itens|
|Entrada|500,00|Almoçar|
E clicar em "Gravar"
Então verei a mensagem "Sucesso ao inserir a movimentação"

Cenário: Gerando uma movimentação de saída dentro do valor limite
Dado que eu esteja autenticado como administrador
E que eu tenha acessado o menu "Movimentações"
E clicado em "Nova movimentação” para inserir uma movimentação
Quando eu informar os dados:
|tipo|valor|itens|
|Saída|100,00|Almoçar|
E clicar em "Gravar"
Então verei a mensagem "Sucesso ao inserir a movimentação"

Cenário: Gerando uma movimentação de saída fora do valor limite
Dado que eu esteja autenticado como administrador
E que eu tenha acessado o menu "Movimentações"
E clicado em "Nova movimentação” para inserir uma movimentação
Quando eu informar os dados:
|tipo|valor|itens|
|Saída|100,01|Almoçar|
E clicar em "Gravar"
Então verei a mensagem "São permitidas apenas movimentações de saída de até 100 reais"

Através da descrição em BDD o desenvolvedor consegue ter uma noção exata de como a aplicação deve funcionar. É com certeza é muito mais completa do que o e-mail descrito anteriormente. Esta descrição servirá como documentação do software, de modo que usuários técnicos ou de negócio poderão entende-la facilmente. Ela servirá, inclusive, para testar o software, validando que o que foi pedido pelo cliente foi implementado da forma correta.

Testes manuais: como deveriam ocorrer e quais são suas limitações

Os testes manuais servem para testar, sem o uso de ferramentas automatizadas, se o software atende as necessidades do cliente e se funciona como esperado.

A questão é, servimos muitos clientes, como, por exemplo: O usuário final, os desenvolvedores, as empresas que consumirão nossa API, entre outros.

Testar um software sob todas estas perspectivas geralmente tende a ser trabalhoso, custoso, demorado, repetitivo e muitas vezes inviável (por exemplo, quando falamos em testar o código de forma manual, pois temos muitas possibilidades para cada método). Podemos dizer que estas seriam suas limitações.

Mesmo assim, ainda ocorrem testes para avaliar se estas características funcionam como esperado.

Testar manualmente, neste caso, exige que o testador tenha as habilidades necessárias para atuar nestas áreas e destreza para saber priorizar os testes que deverão ser executados, visando colaborar para que o cliente receba o software que o ajudará a obter um benefício para seu negócio.

Testes automatizados para ajudar a remover limitações

Testes automatizados são scripts programados para avaliar os softwares, a ordem e os cenários de testes são baseados nos testes que teriam de ser feitos manualamente e basicamente servem para tornar mais ágil o processo de testar um software, mas também nos traz uma série de benefícios, como: Segurança mesmo após fazer alterações drásticas no sistema, redução de debugs no código, velocidade na liberação de versões, economia no tempo dos testadores manuais.

Existe uma série de ferramentas usadas para executar esta atividade, e ferramentas que servem para automatizar testes em todas as camadas mencionadas no tópico anterior, inclusive open-sources, e em várias linguagens de programação.

Vejamos aguns exemplos de frameworks PHP usados para automação de testes.

PHPUnit

Usado para automatizar testes de unidade em PHP, é muito conhecida e utilizada por muitas empresas. Usamos para verificar que os métodos e classes funcionam como esperado quando estão isoladas das demais classes do sistema, veja:

class PedidoTest extends PHPUnit_Framework_TestCase
{
	/**
	 * @test
	 * @author Julio
	*/
	public function aoAdicionarProdutoAListaDeveraConterApenas1Item()
	{
		// Arrange
		$pedido = new Pedido();
		$produto = $this->getMockBuilder("IProduto")
		->setConstructorArgs(
				array(1, "PlayStation", 5, 999.99)
		)
		->getMock();
		// Act
		$pedido->addItemPedido($produto, 2);
		$pedidoitens = $pedido->getPedidoItens();
		// Assert
		$this->assertCount(1, $pedidoitens);
		$this->assertEquals(array($produto, 2), $pedidoitens[0]);
	}
}

Em testes de unidade, usamos o padrão AAA (Arrange, Act e Assert), que corresponde a Preparação, Ação e Asserção.

Neste caso, estamos testando a classe pedido de forma isolada pois ela se integra à classe Produto. Então fizemos um mock da classe Produto para poder testar a classe Pedido antes que a classe Produto terminasse de ser desenvolvida.

PHPUnit com Guzzle

Guzzle é um framework usado para fazer requisições à APIs Rest. Quando a usamos em conjunto com o PHPUnit, temos uma combinação que nos dá a possibilidade de automatizar os testes das APIs, vejamos um exemplo:

class PedidoAPITest extends PHPUnit_Framework_TestCase
{
	/**
	 * @test
	 * @author Julio
	*/
	public function testAcessarRootEntaoReceber200SucessoECodigo()
	{
		// Arrange
		$client = new GuzzleHttp\Client(
     			["base_uri" => "http://localhost:8888/qualister-php-testing/api"]
     	);
		// Act
		$resposta = $this->client->get("/", ['auth' => ['phptesting', '123']]);
		// Assert
		$this->assertEquals(200, $resposta->getStatusCode());
		$body = json_decode($resposta->getBody(), true);
		$this->assertEquals("gnitsetphp", $body["data"]["codigo"]);
	}
}

Veja que, neste caso, usamos a estrutura e asserções do PHP para validar se a API funciona como esperado.

PHPUnit com Selenium WebDriver

Selenium WebDriver é um framework usado para simular interações com aplicações web em um browser real ou headless, através dele conseguimos avaliar se a aplicação funciona corretamente na perspectiva do usuário, veja:

class PedidoWebTest extends PHPUnit_Framework_TestCase
{
	/**
	 * @test
	 * @author Julio
	*/
	public function acessarPaginaInicialValidarMensagensDeAlerta()
	{
		// Arrange
		$driver = RemoteWebDriver::create("http://localhost:4444/wd/hub", DesiredCapabilities::firefox());
		$driver->manage()->window()->maximize();
		$driver->get("http://localhost:8888/qualister-php-testing/web/");
		// Act
		$driver->findElement(WebDriverBy::id("meuspedidos"))->click();
		$driver->switchTo()->alert()->accept();
		$texto = $driver->switchTo()->alert()->getText();
		$driver->switchTo()->alert()->accept();
		// Arrange
		$this->assertContains("Faça login para ver seus pedidos", $texto);
	}
}

É possível digitar, capturar textos, fazer Drag and Drop, e outras várias possibilidades.

Muitos frameworks e uma curva de aprendizagem maior

Como vimos, existem muitos frameworks PHP utilizados para automatizar testes, mas cada um deles tem suas características e uma forma específica de escrita. A curva de aprendizagem torna-se maior quanto mais frameworks utilizamos.

E mais, vimos ainda que em todos eles a forma de organização dos testes utilizava linguagem técnica e não de negócios.

Codeception e sua DSL Jedi

O Codeception é um framework criado pelo @Davert e por mais um time de 7 programadores, que tem o objetivo de facilitar a automação de testes em todas as camadas de um software, escrevendo seguindo o padrão BDD.

Veja um exemplo:

<?php
	$I = new AcceptanceTester($scenario);
	$I->am('Administrador de uma loja'); 
	$I->wantTo('gerenciar movimentações');
	$I->lookForwardTo('Ver, de forma simples, as entradas e saídas de valores');
	$I->amOnPage('/');
	$I->fillField('usuariologin', 'teste');
	$I->fillField('usuariosenha', '123');
	$I->click('Entrar');
	$I->click('Movimentações');
	$I->click('Nova movimentação');
	$I->selectOption("select", "Entrada");
	$I->fillField('movimentacaovalor', '500,00');
	$I->fillField('movimentacaoitens', 'Almoçar');
	$I->click('Gravar');
	$I->see('Sucesso ao inserir a movimentação');
?>

Para usar o Codeception basta baixar o arquivo codecept.phar e adiciona-lo na raiz do diretório do projeto, no nosso caso, "PHPConferenceBr". E então rodar o comando "php codecept.phar bootstrap" para criar a arquitetura inicial.

Automatizando Unidades

  1. Execute o comando "php codecept.phar generate:test unit Pedido" para criar uma classe de teste de unidade chamada "PedidoTest.php";
  2. Adicione os requires às classes que serão testadas:
  3. require './src/Pedido.php';
    require './src/PedidoServicos.php';
    	
  4. Adicione os comandos abaixo no PedidoTest.php contido na pasta tests/unit:
  5. public function testAdicionarProdutoNoPedidoValidarQueFoiAdicionado()
    {
        // Arrange
        $pedido = new Pedido();
        $pedidoServicos = \Codeception\Util\Stub::make('PedidoServicos', ['salvar' => 'Sucesso']);
        // Act
        $resposta = $pedidoServicos->salvar($pedido);
        // Assert
        $this->tester->assertEquals('Sucesso', $resposta);
    }
    	
  6. Execute-o digitando o comando "php codecept.phar run unit".

Automatizando API

  1. Execute o comando "php codecept.phar generate:suite api" para gerar a suite de API
  2. Adicione as configurações dos testes de API no arquivo api.suite.yml:
  3. class_name: ApiTester
    modules:
        enabled:
            - REST:
                 url: 'http://localhost:8888/qualister-php-testing/api'
                 depends: PhpBrowser
    	
  4. Execute o comando "php codecept.phar build" para que o projeto seja atualizado e construídas as dependências referentes à suite de APIs
  5. Execute o comando "php codecept.phar generate:cept api NovoPedido" para criar um teste de API
  6. Adicione os comandos abaixo no NovoPedidoCept.php contido na pasta tests/api:
  7. <?php
    	$I = new ApiTester($scenario);
    	$I->wantTo('adicionar um novo pedido');
    	$I->amHttpAuthenticated('phptesting', '123');
    	$I->sendPOST('/pedido', [
    	       'produtoid' => 10,
    	       'produtonome' => 'Firefox',
    	       'produtoestoque' => 50,
    	       'produtovalor' => 49.90
    	]);
    	$I->seeResponseCodeIs(200);
    	$I->seeResponseIsJson();
    	$I->seeResponseContainsJson(['message' => 'Sucesso', 'status' =>
    	'sucesso']);
    ?>
    	
  8. Execute-o digitando o comando "php codecept.phar run api --steps".

Automatizando Web em 3 passos

  1. Execute o comando "php codecept.phar generate:cept acceptance NovoPedido" para criar um teste de aceitação web
  2. Adicione as configurações dos testes de Aceitação no arquivo acceptance.suite.yml:
  3. class_name: AcceptanceTester
    modules:
        enabled:
            - PhpBrowser:
                url: 'http://localhost:8888/qualister-php-testing/web'
            - \Helper\Acceptance
    	
  4. Adicione os comandos abaixo no NovoPedidoCept.php contido na pasta tests/acceptance:
  5. <?php
    	$I = new AcceptanceTester($scenario);
    	$I->wantTo('adicionar um novo pedido');
    	$I->amOnPage('/');
    	$I->click('Novo pedido');
    	$I->fillField('id', 1);
    	$I->selectOption('produto', 'Firefox');
    	$I->fillField('estoque', 50);
    	$I->fillField('valor', 49.90);
    	$I->click('button');
    	$I->expect('a mensagem "sucesso" seja apresentada');
    	$I->see('Sucesso');
    ?>
    	
  6. Execute-o digitando o comando "php codecept.phar run acceptance --steps".

É possível também executar os testes utilizando browsers reais, ao invés de utilizar testes com browsers headless, neste caso, usa-se o Selenium WebDriver para executar tal ação, veja os passos:

  1. Baixe o Selenium Server Standalone em https://selenium-release.storage.googleapis.com/i... e mova-o para a pasta "bin" dentro do projeto
  2. Starte o Selenium Server usando o comando "java -jar bin/selenium-server-standalone-2.46.0.jar"
  3. Altera as configurações dos testes de Aceitação no arquivo acceptance.suite.yml:
  4. class_name: AcceptanceTester
    modules:
        enabled:
            - WebDriver:
                url: 'http://localhost:8888/qualister-php-testing/web'
                browser: firefox
                window_size: maximize
            - \Helper\Acceptance
    	
  5. Execute-o digitando o comando "php codecept.phar run acceptance --steps".

Rodando todos os testes e gerando relatório de execução

php codecept.phar run --html

O relatório é gerado dentro do diretório test/_output, e é semelhante à imagem a seguir, veja:

Referências

Slides da palestra ministrada no PHP Conference Brasil

Este post foi a fonte da inspiração para a palestra que tive a honra de ministrar na PHP Conference Brasil (phpconference.com.br) em dezembro de 2015.

Slides da palestra: http://goo.gl/D88UZX

POSTS RELACIONADOS

Workshop Gratuito de PHP Testing (Online)

Automatize os testes de unidade, API e Web com frameworks open-source escrevendo em PHP

AGENDA

CURSOS RELACIONADOS