quinta-feira, 28 de fevereiro de 2013

Testando suas classes com PHPUnit

Hoje vou replicar um post do meu irmão(http://developer-v.blogspot.com.br), sim meu irmão também tem um blog e ele é da minha area. 

Hy, folks!!!
E ai galera, finalmente vamos fazer um post, mão-na-massa, o que seria isso ???


????????????????????????
Simples um teste em classes, vamos usar agora o PHPunit para fazer um teste que ira funcionar realmente.

O que é Unit Testing?

- Testes para pequenos trechos de código (unidades)
- Verifica se o comportamento de classes e funções é o esperado
- Caso erros são encontrados exceções são lançadas
- Não interfere com o seu código-fonte

Qual a vantagem?

- Os testes são automatizados
- Eles são executados continuamente durante o ciclo de desenvolvimento
- Detectam falhas tanto de digitação e lógica e também comportamentos inesperados

Exemplo


  1. class Classe1
  2. {
  3.     public function somar($a, $b)
  4.     {
  5.         return $a+$b;
  6.     }
  7.        
  8.     public function subtrair($a, $b)
  9.     {
  10.         return $a-$b;
  11.     }
  12. }
  13. // Teste da classe
  14. $foo = new Classe1();
  15. $soma = $foo->somar(2, 2);
  16. $subracao = $foo->subtrair(2, 2);
  17. echo '2 mais 2 = ' . $soma;
  18. echo '2 menos 2 = ' . $subtracao;
  19. // Funciona conforme esperado!


Apesar do exemplo simples, ilutra uma prática bem comum: a de inserir vários echo() e print() para confirmar o funcionamento de uma classe ou função.

O PHPUnit é uma framework que tem como objetivo automatizar e padronizar a forma como nosso código é testado.

"Whenever you are tempted to type something into a print statement or a debugger expression, write it as a test instead." -- Martin Fowler

Ou seja: "Toda vez que sentir vontade de escrever um print ou outra expressão de debug, pare e crie um teste!"


  1. // Especifique o caminho do PHPUnit
  2. require_once '/usr/share/php/PHPUnit/Framework.php';
  3. // Especifique o caminho da classe a ser testada
  4. require_once 'Classe1.php';
  5. // Como a classe a ser testada se chama "Classe1", a classe de teste deverá se chamar "ClasseTest" e (neste caso) irá herdar de PHPUnit_Framework_TestCase
  6. class Classe1Test extends PHPUnit_Framework_TestCase
  7. {
  8.         // Funções de teste devem ter o prefixo "test"
  9.         public function testSoma()
  10.         {
  11.                 // A classe a ser testada é instanciada
  12.                 $foo = new Classe1();
  13.                 // Testamos a afirmação que "4" é o resultado de passar "2" e "2" (ou seja, 2 + 2 = 4)
  14.                 $this->assertEquals('4', $foo->somar(2, 2));
  15.         }
  16.        
  17.         public function testSubtracao()
  18.         {
  19.                 // idem (veja função acima)
  20.                 $foo = new Classe1();
  21.                 $this->assertEquals('0', $foo->subtrair(2, 2));
  22.         }
  23. }


O resultado:

  1. PHPUnit 3.3.16 by Sebastian Bergmann.
  2. .
  3. Time: 0 seconds
  4. OK (2 tests, 2 assertions)
  5. // Funciona conforme esperado!


O que aconteceu?
Criamos uma classe (Classe1Test) de teste para testar a nossa classe (Classe1) e usamos o PHPUnit para testar o funcionamento esperado

Não é mais complicado?
- Sim e não! Inicialmente pode parecer bem mais simples inserir alguns print() ou echo() e verificar manaulmente, mas ao longo prazo usar o PHPUnit traz diversas vantagens:
- Os testes ficam separados do código, assim não corremos o risco de esqueçer testes no meio do código. Devido à separação também não é necessário ficar voltando e comentando e descomentando as linhas de teste inseridas manualmente.
- Para aplicações um pouco mais complexas (por exemplo com várias classes interagindo juntas ou com AJAX) as vezes não é possível colocar echo() ou print() em qualquer lugar. Até mesmo o FirePHP gera erros de 'header'!
- Usando o PHPUnit criamos vários testes e sempre que desejarmos podemos rodá-los novamente para testar modificações.
- E o melhor de tudo, não precisamos fazer nada na hora de colocar a aplicação no ar. Simplesmente não fazemos o upload da pasta de testes! O código-fonte original segue intacto.

Como funciona?
- Crie uma classe de teste para testar uma classe já existente
- Excute os testes no terminal (ou prompt de comando) e veja os resultados
- A melhor maneira da aprender isto é através de exemplos então vamos instalar o PHPUnit e criar alguns testes!


Instalação do PHPUnit

Quem usa o linux pode fazer facilmente:


  1. # se não tiver o PEAR instalado
  2. apt-get install pear
  3. # descubra o canal do PHPUnit
  4. pear channel-discover pear.phpunit.de
  5. # instale o phpunit
  6. pear install phpunit/PHPUnit
  7. # Ele fica instalado por padrão no /usr/share/php/PHPUnit
  8. # Agora é só incluir o framework nos arquivos de teste e começar!


Quem usa windows procure no google! Não deve ser muito complicado... (se não me engano ele já vem incluso no pacote XAMPP)

Guia para criar Unit Tests

- Os testes para a classe "Classe1" (Classe1.php) deverão ser localizados dentro da classe "Classe1Test" (Classe1Test.php), sempre use o sufixo "Test" (NomeDaClasseTest)
- Na maioria dos casos a classe de teste irá herdar (extends) da classe PHPUnit_Framework_TestCase
- Os testes deverão ser funções públicas com o prefixo "test" (testSomar, testSubtrair, etc)
- Os testes são baseados em afirmações, ou seja: afirmamos que o resultado esperado da função X é Y, e testamos a afirmação pra ver se ela procede.
- Usamos funções como assertEquals() para testar afirmações (exemplos a seguir)

Algumas Afirmações disponíveis (Funções de Teste)

- Existem dezenas (veja a documentação http://www.phpunit.de/manual/), seguem algumas das mais comuns:
- assertEquals($valor, funcao()) (afirma que o valor retornado da funcao é igual a $valor)
- assertNotEquals($valor, funcao()) (afirma que o valor retornado da funcao não é igual a $valor)
- assertSame($valor, funcao()) (testa se o objeto retornado é igual a $valor)
- assertTrue(funcao()) (afirma que o valor retornado é === true)
- assertFalse(funcao()) (=== false)
- setExpectedException (avisa sobre uma exceção que deverá ser lançada)

Como rodar os testes

Através do terminal (ou prompt de comando), no diretório do arquivo de teste digite:


  1. phpunit NomeDaClasseTest
  2. # para rodar os testes contidos no arquivo NomeDaClasseTest.php


Vamos ver alguns exemplos na próxima página.
O test seguinte usa a função assertEquals() para checar o retorno de um método:

  1. // Especifique o caminho do PHPUnit
  2. require_once '/usr/share/php/PHPUnit/Framework.php';
  3. // Especifique o caminho da classe a ser testada
  4. require_once 'Classe2.php';
  5. class Classe2Test extends PHPUnit_Framework_TestCase
  6. {
  7.     public function testCumprimentarSemParametro()
  8.     {
  9.         // O comportamento esperado é que retorne "Olá fulano", testa esta afirmação
  10.         $this->assertEquals('Olá fulano', $foo->cumprimentar());
  11.     }
  12.     public function testCumprimentarComParametro()
  13.     {
  14.         // Instancia a classe e passa "Alex" ao construtor
  15.         $foo = new Classe2('Alex');
  16.         // O comportamento esperado é que retorne "Olá Alex", testa esta afirmação
  17.         $this->assertEquals('Olá Alex', $foo->cumprimentar());
  18.     }
  19.    
  20.         // Funções de teste também podem ser identificados pela anotação @test no DocBlock
  21.         /**
  22.          * @test
  23.          */
  24.          public function vaiDarErrado()
  25.          {
  26.                  // Instancia a classe e passa "ciclano" ao construtor
  27.                 $foo = new Classe2('ciclano');
  28.                 // O comportamento esperado é que retorne "Olá ciclano", mas vamos afirmar que retornará outro valor para forçar um erro
  29.                 $this->assertEquals('Olá Alex', $foo->cumprimentar());
  30.          }
  31. }


Outro teste simples que retorna o seguinte:

  1. PHPUnit 3.3.16 by Sebastian Bergmann.
  2. ..F
  3. Time: 0 seconds
  4. There was 1 failure:
  5. 1) vaiDarErrado(Classe2Test)
  6. Failed asserting that two strings are equal.
  7. expected string <Olá Alex>
  8. difference      <     xxxx???>
  9. got string      <Olá ciclano>
  10. /caminho/para/o/arquivo/Classe2Test.php:35
  11. FAILURES!
  12. Tests: 3Assertions: 3Failures: 1.


Ou seja, quando um teste falha ele informa o arquivo e linha onde ocorreu o erro.
Explica também qual foi o erro:
- O valor esperado era "Ola Alex"
- O valor retornado foi "Ola ciclano"
E ele ainda mostra a diferença ( xxxx???)

Fornecendo valores para os testes

- As vezes não basta só um teste com um valor estático
- Podemos criar uma função para alimentar o teste usando vários valores (e nada impede que sejam de fontes externas)
- A função é identificada através da anotação @dataProvider no DocBlock

Exemplo:

Uma versão um pouco mais complicada para explicar o uso do data provider em testes:

  1. // Demonstração de "Data Providers"
  2. class Classe3Test extends PHPUnit_Framework_TestCase
  3. {
  4.     // Informa o nome do forneçedor de dados (@dataProvider) para a clase de teste
  5.     /**
  6.      * @dataProvider provider
  7.      */
  8.     // Recebe 3 parâmetros
  9.     public function testCombine($a, $b, $c)
  10.     {
  11.         // Afirma que o valor do terceiro parâmetro é igual a concatenação dos primeiros dois separados por um espaço
  12.         $this->assertEquals($c, $a . ' ' . $b);
  13.     }
  14.     // A função fornecedora de valores
  15.     public function provider()
  16.     {
  17.         // retorna um array contendo 3 grupos de valores a serem testados
  18.         return array(
  19.             array('Hello', 'World', 'Hello World'),
  20.             array('É', 'Nois', 'É Nois'),
  21.             array('Deu', 'Errado', 'Deu Certo')
  22.         );
  23.     }
  24. }


Valor retornado:

  1. PHPUnit 3.3.16 by Sebastian Bergmann.
  2. ..F
  3. Time: 0 seconds
  4. There was 1 failure:
  5. 1) testCombine(Classe3Test) with data set #2 ('Deu''Errado''Deu Certo')
  6. Failed asserting that two strings are equal.
  7. expected string <Deu Certo>
  8. difference      <    xxxxx?>
  9. got string      <Deu Errado>
  10. /home/alex/Documents/aw/Articles/PHPUnit/Classe3Test.php:16
  11. FAILURES!
  12. Tests: 3Assertions: 3Failures: 1.


Resumindo...

- O assunto é bastante extenso e realmente fica difícil cobrir todas as possibilidades (que são quase infinitas)
- É possível criar testes para testar se Exceções são lançadas conforme esperado, para testar classes de acesso ao banco de dados e muito mais
- O ideal seria implementar isto em uma aplicação que esteja desenvolvendo, ou seja, crie uma pasta para os testes e vá criando testes para as suas classes nela

Todo o código-fonte para os exemplos e outros exemplos mais complexos (todos bem documentados) estão disponíveis:

Espero que tenham gostado e que eu tenha introduzido bem este assunto que é frequentemente subestimado e importantíssimo quande se trata de desenvolvimento.

Este post, foi melhorado para uma linguagem mais visual e facíl de entender =D...

Fonte PHPbr