Próximo post

Criando campos condicionais no Drupal 7

Leia mais»
jun 20 2010

Testando as suas classes com PHPUnit

Artigo originalmente escrito para o PHPBrasil, apenas o trouxe para cá!

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
  • São executados continuamente durante o ciclo de desenvolvimento
  • Detectam falhas tanto de digitação e lógica e também comportamentos inesperados
  • Escalam conforme o desenvolvimento

Exemplo

1
2
3
4
56
7
8
9
1011
12
require_once('Classe1.php');
// Teste da classe
 
$foo = new Classe1();
 $soma = $foo->somar(2, 2);
$subracao = $foo->subtrair(2, 2);
 
echo '2 mais 2 = ' . $soma;
echo '2 menos 2 = ' . $subtracao; 
// 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!"

Testando a mesma classe do exemplo anterior com o PHPUnit

1
2
3
4
56
7
8
9
1011
12
13
14
1516
17
18
19
2021
22
// Especifique o caminho do PHPUnit
require_once '/usr/share/php/PHPUnit/Framework.php';
// Especifique o caminho da classe a ser testada
require_once 'Classe1.php';
 // 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
class Classe1Test extends PHPUnit_Framework_TestCase {
  // Funções de teste devem ter o prefixo "test"
  public function testSoma() {    // A classe a ser testada é instanciada
    $foo = new Classe1();
    // Testamos a afirmação que "4" é o resultado de passar "2" e "2" (ou seja, 2 + 2 = 4)
    $this->assertEquals('4', $foo->somar(2, 2));
  } 
  public function testSubtracao() {
    // idem (veja função acima)
    $foo = new Classe1();
    $this->assertEquals('0', $foo->subtrair(2, 2));  }
}

O resultado:

1
2
3
4
56
7
8
9
PHPUnit 3.3.16 by Sebastian Bergmann.
 
.
 
Time: 0 seconds 
OK (2 tests, 2 assertions)
 
# 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?

  1. Crie uma classe de teste para testar uma classe já existente
  2. Execute os testes no terminal (ou prompt de comando) e veja os resultados
  3. 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
2
3
4
56
7
8
9
1011
# se não tiver o PEAR instalado
sudo apt-get install pear
 
# descubra o canal do PHPUnit
sudo pear channel-discover pear.phpunit.de 
# instale o phpunit
sudo pear install phpunit/PHPUnit
 
# Ele fica instalado por padrão no /usr/share/php/PHPUnit# 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), 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
2
3
4
5
# para rodar os testes contidos no arquivo NomeDaClasseTest.php
phpunit NomeDaClasseTest
 
# também é possível rodar o PHPUnit nos conteudos de uma pasta inteira
phpunit NomeDaPasta/

Exemplos O teste seguinte usa a função assertEquals() para checar o retorno de um método:

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

O teste retorna o seguinte:

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

Ou seja, quando um teste falha ele informa o arquivo e linha onde ocorreu o erro e explica também qual foi o erro:

O valor esperado era "Ola Alex" e o valor retornado foi "Ola ciclano".

Fornecendo dados para os testes

As vezes não basta só um teste com um valor estático, neste caso podemos criar uma função para alimentar o teste usando vários valores (e nada impede que sejam de fontes externas como um banco de dados ou arquivo xml!). A função é identificada através da anotação @dataProvider no DocBlock.

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

Resumindo

O assunto é bastante extenso e realmente fica difícil cobrir todas as possibilidades (que são quase infinitas), mas é 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 no link abaixo:

[]s

Downloads:

Se curtiu este post, por favor tome alguns segundos para compartilhá-lo usando os links do lado esquerdo! Valeu!!!