Prev Next

Capítulo 13. Desenvolvimento Guiado por Comportamento

Em [Astels2006], Dave Astels aponta o seguinte:

  • Programação Extrema tinha originalmente a regra de testar tudo que pudesse possivelmente quebrar.

  • Porém agora a prática dos testes na Programação Extrema evoluiu para o Desenvolvimento Guiado por Testes (veja Capítulo 12).

  • Mas as ferramentas ainda forçam os desenvolvedores a pesar em termos de testes e asserções em vez de especificações.

 

Então se não é sobre testes, é sobre o quê?

É sobre descobrir o quê você está tentando fazer antes de sair desesperado tentando fazê-lo. Você escreve uma especificação que resolve um pequeno aspecto do comportamento de uma forma concisa, inequívoca e executável. É simples assim. Isso significa que você escreve testes? Não. Isso significa que você escreve especificações sobre o quê o seu código terá que fazer. Significa que você especifica o comportamento do seu código antes da hora. Mas não muito antes. De fato, logo antes de você escrever o código é melhor, porque é quando você tem o máximo de informações à mão até chegar a esse ponto. Como no DGT bem feito, você trabalha com pequenos incrementos... especificando um pequeno aspecto do comportamento de cada vez, então implementando-o.

Quando você perceber que é tudo uma questão de especificar os comportamentos e não de escrever testes, seu ponto de vista muda. Repentinamente a ideia de ter uma classe de teste para cada uma das suas classes de produção se torna ridiculamente limitadora. E a ideia de testar cada um de seus métodos com seu próprio método de testes (uma relação 1-1) se torna hilária.

 
  --Dave Astels

O foco do Desenvolvimento Guiado por Comportamento a linguagem e as interações usadas no processo de desenvolvimento de programas. Desenvolvedores Guiados por Comportamento usam suas linguagens nativas em combinação com a linguagem ubíqua de Design Guiado por Domínio para descrever o propósito e o benefício de seus códigos. Isso permite aos desenvolvedores focarem-se no motivo do código ser criado, em vez dos detalhes técnicos, minimizando a tradução entre a linguagem técnica na qual o código é escrito e a linguagem de domínio falada pelos especialistas em domínios".

A Classe PHPUnit_Extensions_Story_TestCase adiciona um framework de história que facilita a definição de uma Linguagem de Domínio Específico para Desenvolvimento Guiado por Comportamento. Pode ser instalada assim:

pear install phpunit/PHPUnit_Story

Dentro de um cenário, given(), when(), e then() representam um passo cada um. and() é do mesmo tipo do passo anterior. Os seguintes métodos são declarados abstract no PHPUnit_Extensions_Story_TestCase e precisam ser implementados:

  • runGiven(&$mundo, $acao, $argumentos)

    ...

  • runWhen(&$mundo, $acao, $argumentos)

    ...

  • runThen(&$mundo, $acao, $argumentos)

    ...

Exemplo do Jogo de Boliche

Nesta seção vamos dar uma olhada no exemplo de uma classe que calcula a pontuação para um jogo de boliche. As regras são as seguintes:

  • O jogo consiste de 10 quadros.

  • Em cada quadro o jogador tem duas oportunidades de derrubar 10 pinos.

  • A pontuação para um quadro é o número total de pinos derrubados, mais bônus para strikes e spares.

  • Um spare é quando um jogador derruba todos os 10 pinos em duas tentativas.

    O bônus para esse quadro é o número de pinos derrubados na próxima rodada.

  • Um strike é quando um jogador derruba todos os pinos na primeira tentativa.

    O bônus para esse quadro é o valor das duas próximas bolas jogadas.

Exemplo 13.1 mostra como as regras acima podem ser escritas como cenários de especificação usando o PHPUnit_Extensions_Story_TestCase.

Exemplo 13.1: Especificação para a classe JogoDeBoliche

<?php
require_once 'PHPUnit/Extensions/Story/TestCase.php';
require_once 'JogoBoliche.php';

class JogoBolicheSpec extends PHPUnit_Extensions_Story_TestCase
{
/**
* @scenario
*/
public function pontuacaoPorJogarNaCanaletaEh0()
{
$this->given('Novo jogo')
->then('Pontuação deve ser', 0);
}

/**
* @scenario
*/
public function pontuacaoParaTodosUmEh20()
{
$this->given('Novo jogo')
->when('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->and('Jogador arremessa', 1)
->then('Pontuação deve ser', 20);
}

/**
* @scenario
*/
public function pontuacaoParaUmSpareE3Eh16()
{
$this->given('Novo jogo')
->when('Jogador arremessa', 5)
->and('Jogador arremessa', 5)
->and('Jogador arremessa', 3)
->then('Pontuação deve ser', 16);
}

/**
* @scenario
*/
public function pontuacaoParaUmStrikeE3E4Eh24()
{
$this->given('Novo jogo')
->when('Jogador arremessa', 10)
->and('Jogador arremessa', 3)
->and('Jogador arremessa', 4)
->then('Pontuação deve ser', 24);
}

/**
* @scenario
*/
public function pontuacaoParaJogoPerfeitoEh300()
{
$this->given('Novo jogo')
->when('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->and('Jogador arremessa', 10)
->then('Pontuação deve ser', 300);
}

public function runGiven(&$mundo, $acao, $argumentos)
{
switch($action) {
case 'New game': {
$world['game'] = new BowlingGame;
$world['rolls'] = 0;
}
break;

default: {
return $this->notImplemented($action);
}
}
}

public function runWhen(&$mundo, $acao, $argumentos)
{
switch($acao) {
case 'Jogador arremessa': {
$mundo['jogo']->arremesso($argumentos[0]);
$mundo['arremessos']++;
}
break;

default: {
return $this->notImplemented($acao);
}
}
}

public function runThen(&$mundo, $acao, $argumentos)
{
switch($acao) {
case 'Pontuação deve ser': {
for ($i = $mundo['rolls']; $i < 20; $i++) {
$mundo['jogo']->arremesso(0);
}

$this->assertEquals($argumentos[0], $mundo['jogo']->pontuacao());
}
break;

default: {
return $this->notImplemented($acao);
}
}
}
}
?>
phpunit --printer PHPUnit_Extensions_Story_ResultPrinter_Text EspecJogoBoliche
PHPUnit 3.8.0 by Sebastian Bergmann.

EspecJogoBoliche
 [x] Pontuação por jogar na canaleta eh 0

   Given Novo jogo
    Then Pontuação deve ser 0

 [x] Pontuação para todos um eh 20

   Given Novo jogo
    When Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
     and Jogador arremessa 1
    Then Pontuação deve ser 20

 [x] Pontuação para um spare e 3 eh 16

   Given Novo jogo
    When Jogador arremessa 5
     and Jogador arremessa 5
     and Jogador arremessa 3
    Then Pontuação deve ser 16

 [x] Pontuação para um strike e 3 e 4 eh 24

   Given Novo jogo
    When Jogador arremessa 10
     and Jogador arremessa 3
     and Jogador arremessa 4
    Then Pontuação deve ser 24

 [x] Pontuação para jogo perfeito eh 300

   Given Novo jogo
    When Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
     and Jogador arremessa 10
    Then Pontuação deve ser 300

Scenarios: 5, Failed: 0, Skipped: 0, Incomplete: 0.

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