Test-Driven Development with PHP
-
Upload
cezar-souza -
Category
Technology
-
view
837 -
download
3
description
Transcript of Test-Driven Development with PHP
Test Driven Development with PHP
Cezar Junior de SouzaE-mail: [email protected]
Quem sou eu
● Desenvolvedor PHP há 4 anos;
● Bacharel em Sistemas de Informação pela Unochapecó;
● Especialista em Engenharia e Qualidade de Software;
● Programador na Unochapecó;
Quem sou eu
● Tem experiência com:
– Zend Framework 1;
– Zend Framework 2;
– Ruby on Rails 3.2;
– Dojo;
– ExtJs;
– Jquery;
– Doctrine 2;
– PHPUnit;
– Gerência de projetos com SCRUM...
O que vamos ver
● Introdução
– O que é?
– Ciclo
– Por que devemos testar?
– Por que não testamos?
– Testes automatizados
– Testes automatizados X TDD
– Conclusão ● Teste de unidade
– Primeiro teste de unidade
O que vamos ver
● PHPUnit
– O que é?
– Objetivos
– Instalação
– Asserções● assertEquals● assertFalse● assertInstanceOf● assertCount● assertEmpty● assertNull● assertTrue
–
O que vamos ver
● PHPUnit
– Escrevendo Testes com PHPUnit● Dependência de testes● Provedores de dados● Testando Exceções
O que vamos ver
● Exemplos
– Exemplo 1: Conta Bancária
– Exemplo 2: Carrinho de compras
– Exemplo 3: Nota fiscal● Mocks
● Análise e cobertura de código
● Coding Dojo
– O problema dos números romanos● Brainstorming
● Referências
Introdução
● O que é TDD?
– É uma das práticas de desenvolvimento de software sugeridas por diversas metodologias.
– Prega a ideia de fazer com que o desenvolvedor escreva testes automatizados de maneira constante ao longo do desenvolvimento.
– Sugere que “o desenvolvedor escreva o teste antes mesmo da implementação”.
Introdução
● O que é TDD?
– É desenvolvido organicamente, com o feedback do código executável exibido entre as decisões.
– O desenvolvedor escreve os próprios testes porque não pode esperar 20 vezes por dia por alguém para escrevê-los.
– Utilizando a técnica as baterias de testes tendem a ser maiores, cobrindo mais casos, e garantindo uma maior qualidade externa
Introdução
● O que é o TDD?
– A prática nos ajuda a escrever um software melhor, com mais qualidade, e um código melhor, mais fácil de ser mantido e evoluído.
Introdução
“Toda prática que ajuda a aumentar a qualidade do software produzido deve ser
estudada.”(Aniche, 2012)
Ciclo
Escrever o teste->Teste Falha->Escreve o programa->Teste passa->Refatora
Por que devemos testar?
● É necessária somente uma resposta para esta pergunta, para ter a certeza que o nosso código faz o que deve fazer.
● A quantidade de software que não funciona é incrível.
Por que devemos testar?
● Os Estados Unidos estimam que bugs de software lhes custam aproximadamente 60 bilhões de dólares por ano...
Fonte: Computer World. Study: Buggy software costs users, vendors nearly 60b annu-ally. http://www.computerworld.com/s/article/72245/Study_Buggy_software_costs_users_vendors_nearly_60B_annually.
Por que devemos testar?
● Um erro de software pode matar pessoas
– o foguete Ariane 5 explodiu por um erro de software;
– um hospital panamenho matou pacientes pois seu software para dosagem de remédios errou.
Por que não testamos?
● Não há um desenvolvedor que não saiba que a solução para o problema é testar seus códigos.
● Não testamos, porque testar sai caro.
● Testar sai caro porque estamos pagando “a pessoa” errada para fazer o trabalho.
Por que não testamos?
É interessante a quantidade de tempo que gastamos criando soluções tecnológicas para resolver problemas “dos outros”.
Por que não escrevemos programas que resolvam também os nossos problemas?
Testes automatizados
● Uma maneira para conseguir testar o sistema todo de maneira constante e contínua a um preço justo é automatizando os testes.
● O teste automatizado executaria muito rápido;
● Se ele executa rápido, logo o rodaríamos constantemente;
● Se os rodarmos o tempo todo, descobriríamos os problemas mais cedo, diminuindo o custo que o bug geraria.
Testes automatizados
● Mas a equipe de desenvolvimento não gastará tempo escrevendo código de teste?
● Antes ela só gastava tempo com código de produção, essa equipe ficará menos produtiva?
Testes automatizados
● A resposta para essa pergunta é:
– Se produtividade for medida através do número de linhas de código de produção escritos por dia, talvez o desenvolvedor seja sim menos produtivo, mas, se produtividade for a quantidade de linhas de código de produção sem defeitos escritos por dia, o desenvolvedor será mais produtivo ao utilizar testes automatizados.
O que é produtividade?
Testes automatizados X TDD
● Teste automatizado
Problema Pensa em uma solução
Codifica a solução
Codifica ostestes
Pensa nosPossíveis casos
De erro
Problema Pensa em uma solução
Pensa nosPossíveis casos
De erro
Codifica a solução
Codifica ostestes
Testa
● TDD
Refatora
Testa Refatora
Conclusão
● Um médico, ao longo de uma cirurgia, nunca abre mão de qualidade. Se o paciente falar para ele: “Doutor, o senhor poderia não lavar a mão e terminar a cirurgia 30 minutos mais cedo?”, tenho certeza que o médico negaria na hora. Ele saberia que chegaria ao resultado final mais rápido, mas a chance de um problema é tão grande, que simplesmente não valeria a pena.
Conclusão
● Em nossa área, é justamente o contrário.
● Qual desenvolvedor nunca escreveu um código de má qualidade de maneira consciente?
● Quem nunca escreveu uma “gambiarra"?
● Quem nunca colocou software em produção sem executar o mínimo suficiente de testes para tal?
Conclusão
● Não há desculpas para não testar software.
● A solução para que seus testes sejam sustentáveis é automatizando;
● Testar é divertido, aumenta a qualidade do seu produto, e pode ainda ajudá-lo a identificar trechos de código que foram mal escritos ou projetados;
● Te livram várias vezes da chatice do seu inimigo natural, o “testador”;
● Enfim, é muita vantagem.
TestadorProgramadores
Teste de unidade
● Desenvolvedores, quando pensam em teste de software, geralmente imaginam um teste que cobre o sistema como um todo.
● Um teste de unidade não se preocupa com todo o sistema; ele está interessado apenas em saber se uma pequena parte do sistema funciona.
● Um teste de unidade testa uma única unidade do nosso sistema. Geralmente, em sistemas orientados a objetos, essa unidade é a classe.
Teste de unidade
● A ideia é termos baterias de testes de unidade separadas para cada uma das classes do sistema;
● Cada bateria preocupada apenas com a sua classe.
Teste de unidade
● Desenvolvedores gastam toda sua vida automatizando processos de outras áreas de negócio, criando sistemas para RHs, controle de caixa, entre outros, com o intuito de facilitar a vida daqueles profissionais.
● Testes automatizados são fundamentais para um desenvolvimento de qualidade, sua existência traz diversos benefícios, como aumento da qualidade e a diminuição de bugs em produção.
Por que não criar software que automatize o seu próprio ciclo de trabalho?
Primeiro teste de unidade
● Neste primeiro teste vamos de um simples código baseado em “echo” e vamos até um teste totalmente automatizado;
● Imagine que temos que testar um vetor do PHP, uma pequena funcionalidade a se testar é a função count();
● Para um vetor recém criado esperamos que a função count retorne 0;
● Após adicionarmos um elemento, count deverá retornar 1;
Primeiro teste de unidade
Primeiro teste de unidade
● Testando o vetor parte 1:
<?php$componente = array();// espera-se que $componente esteja vazio.
$componente[] = 'elemento';// espera-se que $componente contenha um elemento.?>
<?php$componente = array();// espera-se que $componente esteja vazio.
$componente[] = 'elemento';// espera-se que $componente contenha um elemento.?>
Primeiro teste de unidade
● Testando o vetor parte 2:– Um jeito bem simples te testar que estamos obtendo os resultados
que esperamos é imprimir o resultado antes e depois de adicionarmos o elemento. Se obtivermos 0 e depois 1, a função count se comporta como o esperado.
<?php$componente = array();echo count($componente). “\n”;
$componente[] = 'elemento';echo count($componente). “\n”;
//Saídas:// 0 //1?>
<?php$componente = array();echo count($componente). “\n”;
$componente[] = 'elemento';echo count($componente). “\n”;
//Saídas:// 0 //1?>
Primeiro teste de unidade
● Testando o vetor parte 3– Vamos mudar de testes que exigem interpretação manual para testes
que podem executar automaticamente. Escrevemos a comparação do valor esperado e do real em nosso código de teste e imprimimos ok se os valores forem iguais. Se alguma vez virmos uma mensagem não ok saberemos que algo está errado.
<?php$componente = array();echo count($componente) == 0 ? “ok \n” : “não ok \n”;
$componente[] = 'elemento';echo count($componente) == 1 ? “ok \n” : “não ok \n”;
//Saídas:// ok//ok?>
<?php$componente = array();echo count($componente) == 0 ? “ok \n” : “não ok \n”;
$componente[] = 'elemento';echo count($componente) == 1 ? “ok \n” : “não ok \n”;
//Saídas:// ok//ok?>
Primeiro teste de unidade
● Testando o vetor parte 4:– Agora fatoramos a saída de comparação dos valores esperado e real
em uma função que gera uma Exception onde há uma discrepância. Isso nos traz dois benefícios: a escrita dos testes se torna mais fácil e só obteremos saída quando algo estiver errado.
<?php$componente = array();assertTrue(count($componente) == 0);
$componente[] = 'elemento';assertTrue(count($componente) == 1);
function assertTrue($condicao){
if (!$condicao) {throw new Exception('Asserção falhou.');
}}//Saídas: ...
<?php$componente = array();assertTrue(count($componente) == 0);
$componente[] = 'elemento';assertTrue(count($componente) == 1);
function assertTrue($condicao){
if (!$condicao) {throw new Exception('Asserção falhou.');
}}//Saídas: ...
Primeiro teste de unidade
● O teste agora está totalmente automatizado. Em vez de apenas testar como fizemos em nossa primeira versão, com esta versão temos um teste automatizado.
● Até agora só tivemos dois testes para o vetor e a função count() . Quando começarmos a testar as numerosas funções array_*() que o PHP oferece, precisaremos escrever um teste para cada uma delas.
● Porém, é muito melhor utilizar o que já existe. Existe um framework com todos esses testes já prontos para utilização, este cara é o PHPUnit.
PHPUnit
● O que é o PHPUnit?
– É um framework open source que automatiza os testes de unidade, executando uma bateria de testes para os desenvolvedores.
PHPUnit
● Objetivos
– O PHPUnit tem objetivos de fazer os testes escritos serem:
● Fácil de aprender a escrever;● Fáceis de escrever;● Fáceis de ler;● Fáceis de executar;● Rápidos de executar;● Isolados;● Combináveis.
PHPUnit
● Instalação
– Composer:● Adicionar o phpunit como uma dependência local
por projeto no arquivo composer.json:
{"require-dev": {
"phpunit/phpunit": "3.7.*"}
}
PHPUnit
● Instalação
– Pear● Também existe a possibilidade de instalar o
PHPUnit pelo pear, a nível de S.O
pear config-set auto_discover 1pear install pear.phpunit.de/PHPUnit
Asserções
● A maioria dos casos de teste escrito para PHPUnit são derivadas indiretamente da classe PHPUnit_Framework_Assert, que contém métodos para verificar automaticamente os valores e relatórios de discrepâncias.
Asserções
● assertEquals
– assertEquals($esperado, $real)
– Relata um erro se as variáveis $esperado e $real não forem iguais.
<?phpclass IgualaTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertEquals(1, 0);}
}?>
<?phpclass IgualaTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertEquals(1, 0);}
}?>
Asserções
● assertFalse
– assertFalse(booleano $condicao)
– Relata um erro se a $condicao for TRUE.
<?phpclass FalsoTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertFalse(TRUE);}
}?>
<?phpclass FalsoTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertFalse(TRUE);}
}?>
Asserções
● assertInstanceOf
– assertInstanceOf($esperado, $real)
– Relata um erro se $real não for uma instância de $esperado.
<?phpclass InstanciaDeTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertInstanceOf('RuntimeException', new Exception);}
}?>
<?phpclass InstanciaDeTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertInstanceOf('RuntimeException', new Exception);}
}?>
Asserções
● assertCount()
– assertCount($contaEsperada, $colecao)
– Relata um erro se o número de elementos em $colecao não for $contaEsperada.
<?phpclass ContaTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertCount(0, array('foo'));}
}?>
<?phpclass ContaTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertCount(0, array('foo'));}
}?>
Asserções
● assertEmpty()
– assertEmpty($colecao)
– Relata um erro se $colecao não estiver vazio.
<?phpclass VazioTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertEmpty(array('foo'));}
}?>
<?phpclass VazioTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertEmpty(array('foo'));}
}?>
Asserções
● assertNull
– assertNull($variavel)
– Relata um erro se $variavel não for NULL.
<?phpclass NuloTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertNull('foo');}
}?>
<?phpclass NuloTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertNull('foo');}
}?>
Asserções
● assertTrue
– assertTrue(booleano $condicao)
– Relata um erro se $condicao é FALSE.
<?phpclass VerdadeiroTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertTrue(FALSE);}
}?>
<?phpclass VerdadeiroTest extends PHPUnit_Framework_TestCase{
public function testFalha(){
$this->assertTrue(FALSE);}
}?>
Asserções
● Mais asserções:
– http://phpunit.de/manual/3.7/pt_br/index.html
Escrevendo Testes com PHPUnit
● Testando o vetor com PHPUnit
<?phpclass VetorTest extends PHPUnit_Framework_TestCase{
public function testVazio(){
$componente = array();$this->assertEquals(0, count($componente));
}
public function testPopulado(){
$componente[] = 'elemento';$this->assertInternalType('array', $componente);$this->assertEquals(1, count($componente));
}}
<?phpclass VetorTest extends PHPUnit_Framework_TestCase{
public function testVazio(){
$componente = array();$this->assertEquals(0, count($componente));
}
public function testPopulado(){
$componente[] = 'elemento';$this->assertInternalType('array', $componente);$this->assertEquals(1, count($componente));
}}
Escrevendo Testes com PHPUnit
● Dependência de testes– O PHPUnit suporta a declaração explícita de dependências entre
métodos de teste, ou seja, você pode informar ao interpretador que um teste depende de outro para funcionar.
Exemplo
● Provedores de dados
– Um método de teste pode aceitar argumentos arbitrários. Esses argumentos devem ser fornecidos por um método provedor de dados
– O método provedor de dados a ser usado é especificado usando a anotação @dataProvider.
Escrevendo Testes com PHPUnit
Exemplo
● Testando Exceções
– Usa-se a anotação @expectedException para testar se uma exceção foi lançada dentro do código de teste.
Escrevendo Testes com PHPUnit
Exemplo
Exemplo 1: Conta Bancaria
● Vamos utilizar os conceitos de TDD em uma classe que representa uma conta bancária. O contrato para a classe ContaBancaria nã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.
● Como estamos utilizando os conceitos de TDD vamos 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.
Exemplo 1: Conta Bancaria
Teste para a classe conta bancaria
Exemplo 1: Conta Bancaria
● Agora vamos escrever 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 getSaldo() da classe ContaBancaria.
Exemplo 1: Conta Bancaria
<?phpclass ContaBancaria{
protected $saldo = 0;public function getSaldo(){
return $this->saldo;}
}?>
<?phpclass 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.
● Para que os testes que asseguram a segunda condição do contrato passem, agora precisamos implementar os métodos sacarDinheiro(), depositarDinheiro(), e setSaldo().
Exemplo 1: Conta Bancaria
Exemplo conta bancaria
Exemplo 1: Conta Bancaria
● Agora os testes que asseguram a segunda condição do contrato também passam.
Exemplo 2: Carrinho de compras
● Neste exemplo vamos discutir como os testes podem efetivamente ajudar desenvolvedores a pensar melhor em relação as classes que estão criando.
● Vamos supor que o nosso projeto atual seja uma loja on-line. Esta loja possuí um carrinho de compras que guarda uma coleção de itens comprados.
● Um item tem as seguintes propriedades: descrição, quantidade e valor unitário. O item também deve ter um método que retorne o valor total.
Exemplo 2: Carrinho de compras
● As classes Item e Carrinho estão desta maneira:
– Item
– CarrinhoDeCompras
Exemplo 2: Carrinho de compras
● O cliente solicitou uma funcionalidade que devolva o valor do item de maior valor dentro desse carrinho de compras. Já pensando nos testes, temos os seguintes cenários:
– Um carrinho sem nenhum item deve retornar zero;
– Se o carrinho só tiver um item, ele mesmo será o item de maior valor;
– Se o carrinho tiver muitos itens, o item de maior valor é o que deve ser retornado.
Exemplo 2: Carrinho de compras
● Seguindo a técnica do TDD, vamos começar pelo cenário mais simples, que nesse caso é o carrinho vazio. Vamos criar um teste para a classe MaiorPreco, responsável por essa tarefa:
Exemplo 2: Carrinho de compras
<?php
require getcwd().'/carrinhodecompras/src/CarrinhoDeCompras.php';require getcwd().'/carrinhodecompras/src/Item.php';require getcwd().'/carrinhodecompras/src/MaiorPreco.php';
class MaiorPrecoTest extends PHPUnit_Framework_TestCase{
public function testDeveRetornarZeroSeCarrinhoVazio(){
$CarrinhoDeCompras = new CarrinhoDeCompras();
$MaiorPreco = new MaiorPreco();$valor = $MaiorPreco->encontra($CarrinhoDeCompras);$this->assertEquals(0.0, $valor);
}}
Exemplo 2: Carrinho de compras
● Fazer este teste passar é muito simples, basta retornar 0;
<?php class MaiorPreco{
public function encontra(CarrinhoDeCompras $carrinho){return 0;
}
}?>
Exemplo 2: Carrinho de compras
● O teste deverá passar, agora vamos escrever o teste que é o caso do carrinho conter apenas um produto:
public function testDeveRetornarValorDoItemSeCarrinhoCom1Elemento(){$CarrinhoDeCompras = new CarrinhoDeCompras();$carrinho = $CarrinhoDeCompras->adiciona(new Item("Kindle", 1, 299.00));
$MaiorPreco = new MaiorPreco();$valor = $MaiorPreco->encontra($carrinho);$this->assertEquals(299.00, $valor);
}
Exemplo 2: Carrinho de compras
● Para este teste passar, vamos ter que escrever um pouco mais de código, mas ele continua sendo simples:
<?php class MaiorPreco{
public function encontra(CarrinhoDeCompras $carrinho){
if(count($carrinho->getItens()) == 0)return 0;
$itens = $carrinho->getItens();return $itens[0]->getValorTotal();
}
}
Exemplo 2: Carrinho de compras
● E finalmente, o cenário que resta, precisamos encontrar o item de maior valor caso o carrinho contenha muitos itens, vamos adicionar mais este teste para a classe:
public function testDeveRetornarMaiorValorSeCarrinhoContemMuitosElementos(){$CarrinhoDeCompras = new CarrinhoDeCompras();$carrinho = $CarrinhoDeCompras->adiciona(new Item("Kindle", 2, 299.00));$carrinho = $CarrinhoDeCompras->adiciona(new Item("Galaxy", 1, 1400.00));$carrinho = $CarrinhoDeCompras->adiciona(new Item("Google Glass", 1, 1100.00));
$MaiorPreco = new MaiorPreco3();$valor = $MaiorPreco->encontra($carrinho);
$this->assertEquals(1400.00, $valor);}
Exemplo 2: Carrinho de compras
● Para este teste passar, na implementação vamos navegar pelos itens da coleção, procurando pelo item de maior valor total:
public function encontra(CarrinhoDeCompras $carrinho){if(count($carrinho) == 0)
return 0;$maior = 0;foreach($carrinho->getItens() as $item){
if($maior < $item->getValorTotal())$maior = $item->getValorTotal();
}return $maior;
}
Exemplo 2: Carrinho de compras
● Com esta implementação, todos os testes devem passar.
Exemplo 2: Carrinho de compras
● Com os testes passando, vamos avaliá-los:
– 1º: O teste instancia a classe MaiorPreco, passa para ela um objeto carrinho e verifica o retorno do método;
– 2º: Podemos ver que todo o cenário montado foi em cima da classe CarrinhoDeCompras, não fazemos nada na classe MaiorPreco;
– 3º: Isso é um mau sinal, a classe MaiorPreco parece inútil já que não houve necessidade de setar atributos ou qualquer outro dado nela;
Exemplo 2: Carrinho de compras
● Por que não implementamos o método encontra dentro da própria classe carrinho?
● Vamos fazer essa mudança, vamos levar a lógica do método encontra dentro do CarrinhoDeCompras, o método ficará assim:
Exemplo 2: Carrinho de compras
public function maiorValor(){if(count($this->itens) == 0)
return 0;$maior = 0;foreach($this->itens as $item){
if($maior < $item->getValorTotal())$maior = $item->getValorTotal();
}return $maior;
}
Exemplo 2: Carrinho de compras
● E os testes:
– CarrinhoDeComprasTest
Exemplo 2: Carrinho de compras
● Agora nosso teste está muito mais claro;
● Ao visualizarmos um teste com uma característica estranha, mudamos nosso design de classes;
● Muitos desenvolvedores afirmam que o TDD pode guiá-los no projeto de classes, mas isso não vai depender muito mais da experiência e conhecimento do desenvolvedor do que a utilização da técnica.
Exemplo 3: Nota Fiscal
● Neste exemplo, vamos, de maneira simplificada, simular a emissão de uma nota fiscal. Uma possível representação de Nota Fiscal e Pedido pode ser semelhante a estas classes:
– NotaFiscal
– Pedido
Exemplo 3: Nota Fiscal
● Vamos imaginar que o processo consiste em gerar a nota, persistir no banco de dados e enviá-la por e-mail;
● Para isso, seguindo os princípios de responsabilidade única, vamos precisar de três outras classes, uma responsável por enviar o e-mail, outra por persistir os dados no banco de dados e mais uma para gerar o cálculo do valor da nota fiscal;
Exemplo 3: Nota Fiscal
● Para melhor compreendimento do exemplo, vamos simplificar às classes de persistência e de e-mail, elas ficaram desta forma:
– Persiste.php
<?php
class Persiste{
public function persistir($nf){//persiste os dados em uma base de dados
}
}
?>
Exemplo 3: Nota Fiscal
– Email.php
<?php
class Email{
public function enviar($nf){//Envia nota para o cliente ou outro sistema
}
}
Exemplo 3: Nota Fiscal
● Agora vamos iniciar a escrita da classe GeradorDeNotaFiscal;
● Vamos imaginar que a regra de cálculo da nota fiscal seja simples, o valor da nota deve ser 18% do valor do pedido;
● Vamos começar pelo teste
Exemplo 3: Nota Fiscal
<?php
require getcwd().'/notafiscal/src/GeradorDeNotaFiscal.php';require getcwd().'/notafiscal/src/Pedido.php';
class GeradorDeNotaFiscalTest extends PHPUnit_Framework_TestCase{
public function testDeveGerarNFComValorDeImpostoDescontado(){$GeradorDeNotaFiscal = new GeradorDeNotaFiscal();$Pedido = new Pedido();$Pedido->setPedido("Bob", 4000, 1);
$NotaFiscal = $GeradorDeNotaFiscal->gerar($Pedido);
$this->assertEquals(4000 * 0.82, $NotaFiscal->getValor());
}
Exemplo 3: Nota Fiscal
● Fazer este teste passar é fácil, basta instanciar uma nota fiscal com 18% a menos do valor do pedido:
<?php
require getcwd().'/notafiscal/src/NotaFiscal.php';require getcwd().'/notafiscal/src/Persiste.php';
class GeradorDeNotaFiscal{
public function gerar($pedido){$NotaFiscal = new NotaFiscal();$NotaFiscal->setNotaFiscal($pedido->getCliente(),
$pedido->getValorTotal() * 0.82, date('Y-m-d'));
return $NotaFiscal;}
}
Exemplo 3: Nota Fiscal
● O teste passa. O próximo passo é persistir os dados dessa nota fiscal. A classe persiste já existe, com seu método persistir(), só precisamos usá-lo. Dessa vez vamos fazer diferente, sem escrever o teste antes, vamos ver como a implementação ficaria
Exemplo 3: Nota Fiscal
Linha 1 Linha 2 Linha 3 Linha 40
2
4
6
8
10
12
Coluna 1
Coluna 2
Coluna 3
<?php
require getcwd().'/notafiscal/src/NotaFiscal.php';require getcwd().'/notafiscal/src/Persiste.php';
class GeradorDeNotaFiscal{
public function gerar($pedido){$NotaFiscal = new NotaFiscal();$NotaFiscal->setNotaFiscal($pedido->
getCliente(), $pedido->getValorTotal() * 0.94, date('Y-m-d'));$Persiste = new Persiste();$Persiste->persistir($NotaFiscal);
return $NotaFiscal;}
}
Exemplo 3: Nota Fiscal
● Ok, mas pera... a gente não implementou a classe Persiste, sua funcionalidade não é responsabilidade da classe que estamos escrevendo, então como podemos testar o seu comportamento?
Exemplo 3: Nota Fiscal
● Mocks
– A ideia dos testes de unidade é testar a classe de maneira isolada, sem qualquer interferência das classes que a rodeiam;
– Em um teste onde as classes estão integradas, qual classe que gerou o problema?
– Não há o porquê de os testes do GeradorDeNotaFiscalTest garantir que o dado foi persistido com sucesso, isso é tarefa da classe Persiste, a tarefa do GeradorDeNotaFiscal é somente invocar o método persistir();
Exemplo 3: Nota Fiscal
● Moks
– Para conseguir fazer o teste passar, vamos criar um “duble” para a classe Persiste, um clone dela, onde vamos simular o que o método persistir deve retornar;
Exemplo 3: Nota Fiscal
● O nosso teste de persistência da classe geradora da nota ficará desta forma:
Exemplo 3: Nota Fiscal
public function testDevePersistirNFGerada(){$mock = $this->mockPeriste();
$GeradorDeNotaFiscal = new GeradorDeNotaFiscal($mock);$Pedido = new Pedido();$Pedido->setPedido("Bob", 4000, 1);$NotaFiscal = $GeradorDeNotaFiscal->gerar($Pedido);
}
private function mockPeriste(){$mock = $this->getMock('Persiste', array('persistir'));$mock->expects($this->any())->method('persistir')->will($this->returnValue(true));return $mock;
}
Exemplo 3: Nota Fiscal
● Para conseguirmos testar utilizando o mock, a classe que simulamos deverá ser uma dependência na classe que testamos, portanto, devemos passar a classe Persiste no construtor da classe GeradorDeNotaFiscal, ela ficará assim:
Exemplo 3: Nota Fiscal
class GeradorDeNotaFiscal{
protected $persiste;
public function __construct($persiste){$this->persiste = $persiste;
}
public function gerar($pedido){$NotaFiscal = new NotaFiscal();$NotaFiscal->setNotaFiscal($pedido->getCliente(),
$pedido->getValorTotal() * 0.82, date('Y-m-d'));$this->persiste->persistir($NotaFiscal);
return $NotaFiscal;}
Exemplo 3: Nota Fiscal
● Mocks
– A arte de mockar as dependências é conhecida como “TDD ao estilo londrino”, isso porquê muitas das discussões importantes na área de mock objects surgiram por lá. Autores famosos como Steve Freeman e Nat Pryce (ambos britânicos) são fãs dessa abordagem.
Exemplo 3: Nota Fiscal
● Mocks são extremamente úteis, com ele não precisamos nos preocupar com o funcionamento de classes que ainda não estão prontas, precisamos nos preocupar somente com a unidade que estamos implementando;
● Podemos criar um mock para a classe e-mail também, mas isso deixo por conta de vocês.
Análise e cobertura de código
● Como você descobre o código que ainda não foi testado ou, em outras palavras, ainda não foi coberto por um teste?
● Como você mede o quanto os testes estão completos?
Análise e cobertura de código
● O PHPUnit permite gerar relatórios que informam quais linhas do seu código foram testadas e quais não estão sendo utilizadas gerando estatísticas de tudo isso.
● Para gerar os relatórios deve ser executado os testes através do comando:– phpvendor/phpunit/phpunit/phpunit.php –coverage-html ./report
contabancaria/test/ContaBancariaTest.php
Coding Dojo
● Um coding dojo é um encontro de desenvolvedores que estão interessados em aprender alguma coisa nova, como uma linguagem de programação diferente, ou técnicas de desenvolvimento diferentes. O objetivo é criar um ambiente tranquilo e favorável a novos aprendizados.
Coding Dojo
● A prática mais comum é juntar alguns desenvolvedores e projetar uma máquina na parede. Dois desenvolvedores sentam em frente ao computador e começam a resolver algum problema pré-determinado, usando a linguagem e/ou a prática que desejam aprender melhor.
● Geralmente esses dois desenvolvedores tem apenas alguns minutos para trabalhar. Enquanto eles trabalham a plateia assiste e eventualmente sugere alterações para o “piloto e co-piloto”. Ao final do tempo , o co-piloto vira piloto, o piloto volta para a plateia, e alguém da plateia se torna “co-piloto”.
Coding Dojo
● No nosso Coding Dojo, vamos resolver o problema dos números romanos utilizando os conceitos vistos até aqui.
O problema dos números romanos
● Numerais romanos foram criados na Roma Antiga e eles foram utilizados em todo o seu império. Os números eram representados por sete diferentes símbolos:
– I, unus, 1, (um)
– V, quinque, 5 (cinco)
– X, decem, 10 (dez)
– L, quinquaginta, 50 (cinquenta)
– C, centum, 100 (cem)
– D, quingenti, 500 (quinhentos)
– M, mille, 1.000 (mil)
O problema dos números romanos
● Para representar outros números, os romanos combinavam estes símbolos, começando do algarismo de maior valor e seguindo as regras:
– Algarismos de menor ou igual valor à direita são somados ao algarismo de maior valor;
– Algarismos de menor valor à esquerda são subtraídos do algarismo de maior valor;
– Nenhum símbolo pode ser repetido lado a lado por mais de 3 vezes.
O problema dos números romanos
● Utilizando conceitos de TDD, desenvolver um software onde dado um numeral romano, o programa deve convertê-lo para o número inteiro correspondente.
O problema dos números romanos
● Primeiro teste:
<?php
require getcwd().'/n_romanos/src/ConversorDeNumeroRomano.php';
class ConversorDeNumeroRomanoTest extends PHPUnit_Framework_TestCase {
public function testDeveEntenderOSimboloI() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("I");
$this->assertEquals(1, $numero);}
}?>
<?php
require getcwd().'/n_romanos/src/ConversorDeNumeroRomano.php';
class ConversorDeNumeroRomanoTest extends PHPUnit_Framework_TestCase {
public function testDeveEntenderOSimboloI() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("I");
$this->assertEquals(1, $numero);}
}?>
O problema dos números romanos
● Implementar a classe ConversorDeNumeroRomano da maneira mais simples para que o primeiro teste passe:
<?php
class ConversorDeNumeroRomano {
public function converte($numeroEmRomano) {return 1;
}}
}?>
<?php
class ConversorDeNumeroRomano {
public function converte($numeroEmRomano) {return 1;
}}
}?>
O problema dos números romanos
● Adicionando o segundo teste:
public function testDeveEntenderOSimboloI() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("I");
$this->assertEquals(1, $numero);}
public function testDeveEntenderOSimboloV() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("V");
$this->assertEquals(5, $numero);}
public function testDeveEntenderOSimboloI() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("I");
$this->assertEquals(1, $numero);}
public function testDeveEntenderOSimboloV() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("V");
$this->assertEquals(5, $numero);}
● Implementar a classe ConversorDeNumeroRomano da maneira mais simples para que os dois primeiros testes passem:
O problema dos números romanos
<?phpclass ConversorDeNumeroRomano {
public function converte($numeroEmRomano) {if($numeroEmRomano == "I")
return 1;else if($numeroEmRomano == "V")
return 5;else
return 0;
}}}?>
<?phpclass ConversorDeNumeroRomano {
public function converte($numeroEmRomano) {if($numeroEmRomano == "I")
return 1;else if($numeroEmRomano == "V")
return 5;else
return 0;
}}}?>
<?phpclass ConversorDeNumeroRomano {
public function converte($numeroEmRomano) {if($numeroEmRomano == "I")
return 1;else if($numeroEmRomano == "V")
return 5;else
return 0;
}}?>
<?phpclass ConversorDeNumeroRomano {
public function converte($numeroEmRomano) {if($numeroEmRomano == "I")
return 1;else if($numeroEmRomano == "V")
return 5;else
return 0;
}}?>
O problema dos números romanos
● Para não precisarmos utilizar vários ifs encadeados ou um switch case vamos armazenar todos os símbolos com seus valores em um vetor, assim contemplamos a primeira parte, que é converter os valores quando o símbolo está sozinho.
class ConversorDeNumeroRomano {protected $converteArray; public function __construct(){
$this->converteArray = array('I' => '1', 'V' => '5', 'X' => '10','L' => '50', 'C' => '100', 'D' => '500','M' => '1000');
}public function converte($numeroEmRomano) {
return $this->converteArray[$numeroEmRomano];}
}
class ConversorDeNumeroRomano {protected $converteArray; public function __construct(){
$this->converteArray = array('I' => '1', 'V' => '5', 'X' => '10','L' => '50', 'C' => '100', 'D' => '500','M' => '1000');
}public function converte($numeroEmRomano) {
return $this->converteArray[$numeroEmRomano];}
}
O problema dos números romanos
● Adicionando o terceiro e quarto teste :
...
public function testDeveEntenderOSimboloII() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("II");
$this->assertEquals(2, $numero);}
public function testDeveEntenderOSimboloIII() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("III");
$this->assertEquals(3, $numero);}
...
public function testDeveEntenderOSimboloII() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("II");
$this->assertEquals(2, $numero);}
public function testDeveEntenderOSimboloIII() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("III");
$this->assertEquals(3, $numero);}
O problema dos números romanos
● Implementando a solução mais simples para fazer todos os testes passarem:
public function converte($numeroEmRomano) {$acumulador = 0;for($i = 0; $i < strlen($numeroEmRomano); $i++) {
$acumulador += $this->converteArray[$numeroEmRomano[$i]];}return $acumulador;
}
public function converte($numeroEmRomano) {$acumulador = 0;for($i = 0; $i < strlen($numeroEmRomano); $i++) {
$acumulador += $this->converteArray[$numeroEmRomano[$i]];}return $acumulador;
}
O problema dos números romanos
● Adicionando testes com números de menor valor a esquerda e a direita:
...
public function testDeveEntenderOSimboloIV() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("IV");
$this->assertEquals(4, $numero);}
public function testDeveEntenderOSimboloXI() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("XI");
$this->assertEquals(11, $numero);}
...
public function testDeveEntenderOSimboloIV() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("IV");
$this->assertEquals(4, $numero);}
public function testDeveEntenderOSimboloXI() {$romano = new ConversorDeNumeroRomano();$numero = $romano->converte("XI");
$this->assertEquals(11, $numero);}
O problema dos números romanos
● Implementando a solução mais simples para fazer todos os testes passarem:
public function converte($numeroEmRomano) {$acumulador = 0;$ultimoVizinhoDaDireita = 0;for($i = strlen($numeroEmRomano) - 1; $i >= 0 ; $i--){
echo $numeroEmRomano[$i]; $atual = $this->converteArray[$numeroEmRomano[$i]];// se o da direita for menor, o multiplicaremos// por -1 para torná-lo negativo$multiplicador = 1;if($atual < $ultimoVizinhoDaDireita) $multiplicador = -1;$acumulador += $atual * $multiplicador;// atualiza o vizinho da direita$ultimoVizinhoDaDireita = $atual;
}return $acumulador;
}
public function converte($numeroEmRomano) {$acumulador = 0;$ultimoVizinhoDaDireita = 0;for($i = strlen($numeroEmRomano) - 1; $i >= 0 ; $i--){
echo $numeroEmRomano[$i]; $atual = $this->converteArray[$numeroEmRomano[$i]];// se o da direita for menor, o multiplicaremos// por -1 para torná-lo negativo$multiplicador = 1;if($atual < $ultimoVizinhoDaDireita) $multiplicador = -1;$acumulador += $atual * $multiplicador;// atualiza o vizinho da direita$ultimoVizinhoDaDireita = $atual;
}return $acumulador;
}
O problema dos números romanos
● Todos os testes já passam, o algoritmo criado até então já atende o cenário do teste.
● Refletindo sobre o assunto
– Que vantagens temos programando assim?● Foco no teste e não na implementação;● Código nasce testado;● Simplicidade;● Melhor reflexão sobre o design da classe.
O problema dos números romanos
Brainstorming
Referências
● http://phpunit.de/manual (2013);
● Bergmann, PHPUnit Manual (2005);
● BECK, Kent. Test Driven Development: By Example, Addison-Wesley, 2002.
● BECK ,Kent. Test Driven Development: By Example. ISBN-10: 0321146530 ISBN-13: 9780321146533. Publisher: Addison-Wesley Professional Copyright: 2003, 220 p.
● Aniche,TDD - Teste e Design no Mundo Real (2012).
● KOSKELA, Lasse. Test Driven: TDD and Acceptance TDD for Java Developers. Manning Publications. 2007, 543 p.
● SOMMERVILLE, Ian. Engenharia de Software. 7. ed. São Paulo: Addison Wesley, 2004.