Prev Next

Capítulo 12. Desenvolvimento Guiado por Testes

Testes Unitários são uma parte vital de várias práticas de desenvolvimento e processos como Programar Testes Primeiro, Programação Extrema, e Desenvolvimento Guiado por Testes. Eles também permitem um Projeto-por-Contrato em linguagens de programação que não suportam essa metodologia com construções de linguagem.

Você pode usar o PHPUnit para escrever testes uma vez que você tiver terminado de programar. Porém quanto antes um teste é escrito depois de um erro ser introduzido, mais valioso o teste se torna. Então em vez de escrever os testes meses depois do código estar "completo", podemos escrever os testes dias ou horas ou minutos depois da possível introdução de um defeito. Por que parar aqui? Por que não escrever os testes um pouco antes da possível introdução de um defeito?

Programar Testes Primeiro, que é parte da Programação Extrema e Desenvolvimento Guiado por Testes, constrói essa ideia e leva isso ao extremo. Com o poder computacional de hoje, temos a oportunidade de executar milhares de testes, milhares de vezes por dia. Nós podemos usar o retorno de todos esses testes para programar em pequenos passos, cada qual levando consigo a segurança de um novo teste automatizado além de todos os testes que vieram antes dele. Os testes são como ancoradores, assegurando que você, não importa o que aconteça, uma vez que tiver feito progresso só poderá cair até aqui.

Quando você escreve o teste primeiro ele não poderá ser executado, pois você estará chamando objetos e métodos que ainda não foram programados. Isso pode parecer estranho no começo, mas depois de um tempo você vai se acostumar. Pense em Programar Testes Primeiro como uma abordagem pragmática para seguir o princípio da programação orientada a objetos de programar para uma interface em vez de programar para uma implementação: enquanto você está escrevendo o teste você está pensando sobre a interface do objeto que está testando -- como esse objeto se parece do lado de fora. Quando você vai fazer o teste realmente funcionar, você está pensando sobre pura implementação. A interface é consertada a partir da falha desse teste.

 

O ponto do Desenvolvimento Guiado por Testes é dirigir a funcionalidade que o programa realmente precisa, em vez do que o programador imagina que provavelmente deverá ter. A forma que isso é feito pode parecer contra-intuitiva no começo, se não totalmente boba, mas não apenas faz sentido, como também rapidamente se torna uma forma natural e elegante de desenvolver programas.

 
  --Dan North

O que segue é necessariamente uma introdução abreviada ao Desenvolvimento Guiado por Testes. Você pode explorar o tópico posteriormente em outros livros, como Test-Driven Development [Beck2002] de Kent Beck ou A Practical Guide to Test-Driven Development de Dave Astels[Astels2003].

Exemplo da Conta Bancária

Nesta seção, vamos dar uma olhada no exemplo de uma classe que representa uma conta bancária. O contrato para a classe ContaBancarianão apenas exige métodos get e set para o saldo da conta, mas também métodos para depositar e sacar dinheiro. Também especifica as duas seguintes condições que devem ser garantidas:

  • O saldo inicial da conta bancária deve ser zero.

  • O saldo da conta bancária não pode se tornar negativo.

Nós escrevemos os testes para a classe ContaBancaria antes de escrevermos o código propriamente dito da classe. Nós usamos as condições do contrato como base para os testes e nomeamos os testes conforme o mesmo, como mostrado em Exemplo 12.1.

Exemplo 12.1: Testes para a classe ContaBancaria

<?php
require_once 'ContaBancaria.php';

class ContaBancariaTest extends PHPUnit_Framework_TestCase
{
protected $cb;

protected function setUp()
{
$this->cb = new ContaBancaria;
}

public function testSaldoInicialZero()
{
$this->assertEquals(0, $this->cb->getSaldo());
}

public function testSaldoNaoPodeFicarNegativo()
{
try {
$this->cb->sacarDinheiro(1);
}

catch (ExcecaoContaBancaria $e) {
$this->assertEquals(0, $this->cb->getSaldo());

return;
}

$this->fail();
}

public function testSaldoNaoPodeFicarNegativo2()
{
try {
$this->cb->depositarDinheiro(-1);
}

catch (ExcecaoContaBancaria $e) {
$this->assertEquals(0, $this->cb->getSaldo());

return;
}

$this->fail();
}
}
?>

Agora escrevemos somente o mínimo de código necessário para o primeiro teste, testSaldoInicialZero(), passar. Em nosso exemplo essa quantidade equivale a implementar o método getBalance() da classe ContaBancaria, como mostrado em Exemplo 12.2.

Exemplo 12.2: Código necessário para que o testSaldoInicialZero() passe

<?php
class ContaBancaria
{
protected $saldo = 0;

public function getSaldo()
{
return $this->saldo;
}
}
?>

O teste para a primeira condição do contrato agora passa, mas os testes para a segunda condição do contrato falham, porque ainda temos que implementar os métodos que esses testes chamam.

phpunit ContaBancariaTest
PHPUnit 3.8.0 by Sebastian Bergmann.

.
Fatal error: Call to undefined method ContaBancaria::sacarDinheiro()

Para que os testes que asseguram a segunda condição do contrato passem, agora precisamos implementar os métodos sacarDinheiro(), depositarDinheiro(), e setSaldo() como mostrado em Exemplo 12.3. Esses métodos são escritos de forma a gerar uma ExcecaoContaBancaria quando forem chamados com valores ilegais que poderiam violar as condições do contrato.

Exemplo 12.3: A classe ContaBancaria completa

<?php
class ContaBancaria
{
protected $saldo = 0;

public function getSaldo()
{
return $this->saldo;
}

protected function setSaldo($saldo)
{
if ($saldo >= 0) {
$this->saldo = $saldo;
} else {
throw new ExcecaoContaBancaria;
}
}

public function depositarDinheiro($saldo)
{
$this->setSaldo($this->getSaldo() + $saldo);

return $this->getSaldo();
}

public function sacarDinheiro($saldo)
{
$this->setSaldo($this->getSaldo() - $saldo);

return $this->getSaldo();
}
}
?>

Agora os testes que asseguram a segunda condição do contrato também passam:

phpunit ContaBancariaTest
PHPUnit 3.8.0 by Sebastian Bergmann.

...

Time: 0 seconds


OK (3 tests, 3 assertions)

Alternativamente, você pode usar os métodos estáticos de asserção fornecidos pela classe PHPUnit_Framework_Assert para escrever as condições do contrato como asserções do tipo Projeto-por-Contrato em seu código, como mostrado em Exemplo 12.4. Quando uma dessas asserções falha, surge uma exceção PHPUnit_Framework_AssertionFailedError Com essa abordagem, você escreve menos código para as verificações das condições do contrato e os testes se tornam mais legíveis. Porém, você adiciona uma dependência em tempo de execução ao PHPUnit no seu projeto.

Exemplo 12.4: A classe ContaBancaria com asserções projetadas-por-contrato

<?php
class ContaBancaria
{
private $saldo = 0;

public function getSaldo()
{
return $this->saldo;
}

protected function setSaldo($saldo)
{
PHPUnit_Framework_Assert::assertTrue($saldo >= 0);

$this->saldo = $saldo;
}

public function depositarDinheiro($quantia)
{
PHPUnit_Framework_Assert::assertTrue($quantia >= 0);

$this->setSaldo($this->getSaldo() + $quantia);

return $this->getSaldo();
}

public function sacarDinheiro($quantia)
{
PHPUnit_Framework_Assert::assertTrue($quantia >= 0);
PHPUnit_Framework_Assert::assertTrue($this->saldo >= $quantia);

$this->setSaldo($this->getSaldo() - $quantia);

return $this->getSaldo();
}
}
?>

Escrevendo as condições do contrato nos testes, temos usado o conceito Projeto-por-Contrato para programar a classe ContaBancaria Então escrevemos, seguindo a abordagem Programe Testes Primeiro, o código precisava fazer os testes passarem. Porém esquecemos de escrever testes que chamem setSaldo(), depositarDinheiro(), e sacarDinheiro() com valores legais que não violem as condições do contrato. Precisamos de um meio para testar nossos testes ou pelo menos medir sua qualidade. Tal meio é a análise da informação de cobertura-de-código que discutiremos a seguir.

Prev Next
1. Automatizando Testes
2. Objetivos do PHPUnit
3. Instalando o PHPUnit
PEAR
Composer
PHP Archive (PHAR)
Pacotes opcionais
Atualizando
4. Escrevendo Testes para o PHPUnit
Dependências de Testes
Provedores de Dados
Testando Exceções
Testando Erros PHP
Testando Saídas
Asserções
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertContainsOnlyInstancesOf()
assertCount()
assertEmpty()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertInstanceOf()
assertInternalType()
assertJsonFileEqualsJsonFile()
assertJsonStringEqualsJsonFile()
assertJsonStringEqualsJsonString()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertStringMatchesFormat()
assertStringMatchesFormatFile()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
Saída de Erro
Casos Extremos
5. O executor de testes em linha-de-comando
Comutadores de linha-de-comando
6. Ambientes
Mais setUp() que tearDown()
Variantes
Compartilhando Ambientes
Estado Global
7. Organizando Testes
Compondo uma Suíte de Testes usando o Sistema de Arquivos
Compondo uma Suíte de Testes Usando uma Configuração XML
8. Testando Bancos de Dados
Fornecedores Suportados para Testes de Banco de Dados
Dificuldades em Testes de Bancos de Dados
Os quatro estágios dos testes com banco de dados
1. Limpar o Banco de Dados
2. Configurar o ambiente
3–5. Executar Teste, Verificar saída e Teardown
Configuração de Caso de Teste de Banco de Dados do PHPUnit
Implementando getConnection()
Implementando getDataSet()
E quanto ao Esquema do Banco de Dados (DDL)?
Dica: Use seu próprio Caso Abstrato de Teste de Banco de Dados
Entendendo Conjunto de Dados e Tabelas de Dados
Implementações disponíveis
Cuidado com Chaves Estrangeiras
Implementando seus próprios Conjuntos de Dados/ Tabelas de Dados
A API de Conexão
API de Asserções de Banco de Dados
Assertando a contagem de linhas de uma Tabela
Assertando o Estado de uma Tabela
Assertando o Resultado de uma Query
Assertando o Estado de Múltiplas Tabelas
Perguntas Mais Frequentes
O PHPUnit vai (re)criar o esquema do banco de dados para cada teste?
Sou forçado a usar PDO em minha aplicação para que a Extensão para Banco de Dados funcione?
O que posso fazer quando recebo um Erro Too much Connections?
Como lidar com NULL usando Conjuntos de Dados XML Plano / CSV?
9. Testes Incompletos e Pulados
Testes Incompletos
Pulando Testes
Pulando Testes usando @requires
10. Dublês de Testes
Esboços (stubs)
Objetos Falsos
Esboçando e Falsificando Serviços Web
Esboçando o Sistema de Arquivos
11. Práticas de Teste
Durante o Desenvolvimento
Durante a Depuração
12. Desenvolvimento Guiado por Testes
Exemplo da Conta Bancária
13. Desenvolvimento Guiado por Comportamento
Exemplo do Jogo de Boliche
14. Análise de Cobertura de Código
Especificando métodos cobertos
Ignorando Blocos de Código
Incluindo e Excluindo Arquivos
Casos Extremos
15. Outros Usos para Testes
Documentação Ágil
Testes Inter-Equipes
16. Gerador de Esqueleto
Gerando um Esqueleto de Classe de Caso de Teste
Gerando uma Classe Esqueleto de uma Classe de Caso de Teste
17. PHPUnit e Selenium
Servidor Selenium
Instalação
PHPUnit_Extensions_Selenium2TestCase
PHPUnit_Extensions_SeleniumTestCase
18. Registrando
Resultados de Teste (XML)
Resultados de Teste (TAP)
Resultados de Teste (JSON)
Cobertura de Código (XML)
Cobertura de Código (TEXTO)
19. Estendendo o PHPUnit
Subclasse PHPUnit_Framework_TestCase
Escreva asserções personalizadas
Implementando PHPUnit_Framework_TestListener
Subclasse PHPUnit_Extensions_TestDecorator
Implementando PHPUnit_Framework_Test
A. Assertions
B. Anotações
@author
@backupGlobals
@backupStaticAttributes
@codeCoverageIgnore*
@covers
@coversNothing
@dataProvider
@depends
@expectedException
@expectedExceptionCode
@expectedExceptionMessage
@group
@outputBuffering
@requires
@runTestsInSeparateProcesses
@runInSeparateProcess
@test
@testdox
@ticket
C. O arquivo de configuração XML
PHPUnit
Suítes de Teste
Grupos
Incluindo e Excluindo Arquivos para Cobertura de Código
Registrando
Ouvintes de Teste
Setting PHP INI settings, Constants and Global Variables
Configurando Navegadores para Selenium RC
D. Índice
E. Bibliografia
F. Copyright