Capítulo 4. Escrevendo Testes para PHPUnit

Exemplo 4.1 mostra como podemos escrever testes utilizando PHPUnit que verifica operações com arrays de PHP. O exemplo apresenta as convenções fundamentais e as etapas para escrever testes com PHPUnit:

  1. Os testes para a classe "Class" vão para uma classe "ClassTest".
  2. ClassTest é herdada (na maioria das vezes) de PHPUnit_Framework_TestCase .
  3. Os testes são métodos públicos que são nomeados test* .

    Alternativamente, você pode usar a anotação @test em um método do bloco de documentação para marcá-lo como um método de teste.
  4. Dentro dos métodos de teste, os métodos de afirmação como assertEquals() (veja a seção chamada "PHPUnit_Framework_Assert" ) são usados para afirmar que o valor real corresponde a um valor esperado.


Exemplo 4.1: operações com matrizes Testes com PHPUnit
<?php
require_once 'PHPUnit/Framework.php';
 
class StackTest extends PHPUnit_Framework_TestCase
{
    public function testPushAndPop()
    {
        $stack = array();
        $this->assertEquals(0, count($stack));
 
        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertEquals(1, count($stack));
 
        $this->assertEquals('foo', array_pop($stack));
        $this->assertEquals(0, count($stack));
    }
}
?>

Exemplo 4.2 mostra uma sintaxe alternativa para a utilização de afirmações que não requer o uso de $this-> .

Exemplo 4.2: operações com matrizes Testes com PHPUnit
<?php
require_once 'PHPUnit/Framework/Assert/Functions.php';
 
class StackTest extends PHPUnit_Framework_TestCase
{
    public function testPushAndPop()
    {
        $stack = array();
        assertEquals(0, count($stack));
 
        array_push($stack, 'foo');
        assertEquals('foo', $stack[count($stack)-1]);
        assertEquals(1, count($stack));
 
        assertEquals('foo', array_pop($stack));
        assertEquals(0, count($stack));
    }
}
?>

Sempre que você for tentado a escrever algo em uma função print ou uma expressão do depurador, escrevê-lo como um teste em seu lugar.
- Martin Fowler

Teste de Dependências

Testes Unitários são essencialmente escritos como uma boa prática para ajudar os desenvolvedores a identificar e corrigir bugs, refatorar código e para servir de documentação para uma unidade de software em teste. Para conseguir esses benefícios, os testes de unidade, idealmente, deveriam cobrir todos os caminhos possíveis em um programa. Um teste de unidade geralmente cobre um caminho específico em uma função ou método. No entanto, um método de ensaio não é necessáriamente encapsulado, uma entidade independente. Muitas vezes, há dependências implícitas entre os métodos de teste, escondidos no cenário de implementação de um teste.
- Adrian Kuhn et. al.

PHPUnit apoia a declaração explícita de dependências entre os métodos de ensaio. Essas dependências não definem a ordem em que os métodos de ensaio devem ser executadas, mas permitem o retorno de uma instância do dispositivo de teste por um produtor e passá-lo para os consumidores dependentes.
  • O produtor é um método de teste que produz a sua unidade em teste como valor de retorno.
  • O consumidor é um método de teste que depende de um ou mais produtores e seus valores de retorno.

Exemplo 4.3 mostra como usar a anotação @depends para expressar dependências entre os métodos de ensaio.

Exemplo 4.3: Usando o @depends de anotação para expressar dependências
<?php
class StackTest extends PHPUnit_Framework_TestCase
{
    public function testEmpty()
    {
        $stack = array();
        $this->assertEmpty($stack);
 
        return $stack;
    }
 
    /**
     * @depends testEmpty
     */
    public function testPush(array $stack)
    {
        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertNotEmpty($stack);
 
        return $stack;
    }
 
    /**
     * @depends testPush
     */
    public function testPop(array $stack)
    {
        $this->assertEquals('foo', array_pop($stack));
        $this->assertEmpty($stack);
    }
}
?>

No exemplo acima, o primeiro teste, testEmpty() , cria um novo array e afirma que ela está vazio. Em seguida, retorna o array testado como seu resultado. O segundo teste, testPush() , depende de testEmpty() e recebe o resultado daquela função como seu argumento. Finalmente, testPop() depende de testPush().

Para localizar rapidamente os defeitos, queremos que nossa atenção seja centrada em testes falhos e relevantes. É por isso que PHPUnit ignora a execução de um teste quando uma dependencia de teste falhou. Isso melhora a localização de defeitos através da exploração das dependências entre os testes, como mostrado no Exemplo 4,4.

Exemplo 4.4: Explorando as dependências entre testes
<?php
class DependencyFailureTest extends PHPUnit_Framework_TestCase
{
    public function testOne()
    {
        $this->assertTrue(FALSE);
    }
 
    /**
     * @depends testOne
     */
    public function testTwo()
    {
    }
}
?>

phpunit --verbose DependencyFailureTest
PHPUnit 3.4.14 by Sebastian Bergmann.

DependencyFailureTest
FS

Time: 0 seconds

There was 1 failure:

1) testOne(DependencyFailureTest)
Failed asserting that is true.
/home/sb/DependencyFailureTest.php:6

There was 1 skipped test:
1) testTwo(DependencyFailureTest)
This test depends on "DependencyFailureTest::testOne" to pass.

FAILURES!
Tests: 2, Assertions: 1, Failures: 1, Skipped: 1.

Um teste pode ter mais de uma anotação @depends. PHPUnit não alterar a ordem em que os testes são executados, você tem que garantir que as dependências de um teste são cumpridas antes do teste ser executado.

Provedores de dados

Um método de teste pode aceitar os argumentos arbitrários. Estes argumentos devem ser providenciados por um método de provedor de dados ( provider() em Exemplo 4,5 ). O método de provedor de dados a ser utilizado é especificado usando a anotação @dataProvider.

Um método de provedor de dados deve ser publico e retornar ou uma matriz de matrizes ou um objeto que implementa a interface "Iterator" e produz uma matriz para cada passo de iteração. Para cada matriz que faz parte da coleção o método de teste será chamado com o conteúdo do array como argumentos.

Exemplo 4.5: Usando os provedores de dados
<?php
class DataTest extends PHPUnit_Framework_TestCase
{
    /**
     * @dataProvider provider
     */
    public function testAdd($a, $b, $c)
    {
        $this->assertEquals($c, $a + $b);
    }
 
    public function provider()
    {
        return array(
          array(0, 0, 0),
          array(0, 1, 1),
          array(1, 0, 1),
          array(1, 1, 3)
        );
    }
}
?>

phpunit DataTest
PHPUnit 3.5.0 by Sebastian Bergmann.

...F

Time: 0 seconds

There was 1 failure:

1) testAdd(DataTest) with data (1, 1, 3)
Failed asserting that  matches expected value .
/home/sb/DataTest.php:21

FAILURES!
Tests: 4, Assertions: 4, Failures: 1.

Nota
When a test receives input from both a @dataProvider method and from one or more tests it @depends on, the arguments from the data provider will come before the ones from depended-upon tests. Quando um teste recebe entrada de ambos, um método @dataProvider e de um ou mais teste de @depends, os argumentos do provedor de dados vêm antes dos testes de dependencia.

Nota
Quando um teste depende de um teste que usa fornecedores de dados, o teste de função será executada se o teste de que ele depende for bem sucedido com pelo menos um conjunto de dados. O resultado de um teste que usa os provedores de dados não pode ser injetado em um teste de função.

Teste de Exceções

Exemplo 4.6 mostra como usar a anotação @expectedException para testar se uma exceção é feita no interior do código testado.

Exemplo 4.6: Usando a anotação @ ExpectedException
<?php
 class   ExceptionTest   extends   PHPUnit_Framework_TestCase 
 { 
      /** 
      * @expectedException InvalidArgumentException 
      */ 
      public   function   testException ( ) 
      { 
      } 
 } 
?>

phpunit ExceptionTest
PHPUnit 3.5.0 by Sebastian Bergmann.

F

Time: 0 seconds

There was 1 failure:

1) testException(ExceptionTest)
Expected exception InvalidArgumentException

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Alternativamente, você pode usar o setExpectedException() método para definir a exceção prevista como mostrado no Exemplo 4.7.

Exemplo 4.7: Demanda que uma exceção seja feita pelo código testado
<?php
class ExceptionTest extends PHPUnit_Framework_TestCase
{
    public function testException()
    {
        $this->setExpectedException('InvalidArgumentException');
    }
}
?>

phpunit ExceptionTest
PHPUnit 3.5.0 by Sebastian Bergmann.

F

Time: 0 seconds

There was 1 failure:

1) testException(ExceptionTest)
Expected exception InvalidArgumentException

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Tabela 4.1 mostra os métodos previstos para testes exceções.

void setExpectedException(string $exceptionName)
Defina o nome da exceção prevista para $exceptionName .

String getExpectedException()
Retornar o nome da exceção esperada. 


Você também pode utilizar o método mostrado na Exemplo 4.8 para testar exceções.

Exemplo 4.8: Abordagem alternativa ao teste exceções Exemplo
<?php
 class   ExceptionTest   extends   PHPUnit_Framework_TestCase   { 
      public   function   testException ( )   { 
          try   { 
              // ... Code that is expected to raise an exception ... 
          } 
 
          catch   ( InvalidArgumentException   $expected )   { 
              return ; 
          } 
 
          $this -> fail ( 'An expected exception has not been raised.' ) ; 
      } 
 } 
?>

Se o código que deverá gerar uma exceção no Exemplo 4.8 , não levantar a exceção esperada, a chamada subseqüente para fail() (ver Tabela 22.2 ) irá interromper o teste e sinalizar um problema com o teste. Se a exceção prevista é gerado, o bloco catch será executado e o teste finalizará com êxito.

PHP Erros Testes

Por padrão PHPUnit converte erros do PHP, avisos e anúncios (notices) que são acionados durante a execução de um teste para uma exceção. Usando essas exceções, você pode, por exemplo, esperar um teste para provocar um erro do PHP, como mostrado no Exemplo 4,9 .

Exemplo 4.9: Esperar um erro de PHP usando ExpectedException @
<?php
 class   ExpectedErrorTest   extends   PHPUnit_Framework_TestCase 
 { 
      /** 
      * @expectedException PHPUnit_Framework_Error 
      */ 
      public   function   testFailingInclude ( ) 
      { 
          include   'not_existing_file.php' ; 
      } 
 } 
?>

phpunit ExpectedErrorTest
PHPUnit 3.5.0 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 1 assertion)

PHPUnit_Framework_Error_Notice e PHPUnit_Framework_Error_Warning representam PHP avisos e advertências, respectivamente.

Capítulo 3. Instalando PHPUnit

PHPUnit 3,5 requer PHP 5.2.7 (ou posterior), mas o PHP 5.3.2 (ou posterior) é altamente recomendado. Ele deve ser instalado usando o instalador pear. Este instalador é a espinha dorsal do PEAR, que provê um sistema de distribuição de pacotes PHP, e é fornecido com cada versão do PHP desde a versão 4.3.0.

O canal PEAR ( pear.phpunit.de ) que é usado para distribuir o PHPUnit precisa ser registrado no ambiente PEAR local. Furthermore, a component that PHPUnit depends upon is hosted on the Symfony Components PEAR channel ( pear.symfony-project.com ). Além disso, um componente que o PHPUnit depende está hospedado no canal de componentes de PEAR do Symfony ( pear.symfony-project.com ).

pear channel-discover pear.phpunit.de

pear channel-discover pear.symfony-project.com

Os comandos acima devem ser feitos apenas uma vez (mas se por acaso você adicionar este mais de uma vez não haverá problemas). Agora o instalador PEAR pode ser usado para instalar os pacotes do canal PHPUnit:

pear install phpunit/PHPUnit

Após a instalação você pode encontrar os arquivos de código fonte do PHPUnit dentro do seu diretório PEAR local, o caminho é geralmente /usr/lib/php/PHPUnit.

Apesar do instalador PEAR ser a única forma de apoio para instalar o PHPUnit, você pode instalar o PHPUnit manualmente. Para a instalação manual, faça o seguinte:

1. Download do arquivo de release de http://pear.phpunit.de/get/ e extraí-o para um diretório que é listado no include_path do seu arquivo de configuração php.ini.
2. Prepare o phpunit script:
a. Renomeie o script phpunit.php para phpunit.
b. Substitua o @php_bin@ string nele com o caminho para o intérprete de linha de comando do PHP (normalmente /usr/bin/php).
c. Copie-o para um diretório que está no seu caminho e o torne executável (chmod +x phpunit).
3. Prepare o script PHPUnit/Util/PHP.php:
a. Substitua o @php_bin@ string nele com o caminho para o intérprete de linha de comando do PHP (normalmente /usr/bin/php ).

Capítulo 2. Objetivos do PHPUnit

Até agora, temos dois testes para o array do PHP e a função sizeof(). Quando começarmos a testar as numerosas funções array_*() que o PHP oferece, teremos de escrever um teste para cada uma delas. Poderíamos escrever a infra-estrutura para todos esses testes a partir do zero.  No entanto, é muito melhor escrever a infra-estrutura de teste de inicio e, em seguida, escrever apenas as partes de cada teste. PHPUnit é essa infra-estrutura.

Um framework como o PHPUnit tem de resolver um conjunto de restrições, algumas das quais parecem estar sempre em conflito uns com os outros. Simultaneamente, os testes devem ser:

Fácil de aprender a se escrever
Se os testes não são fáceis de escrever, os desenvolvedores não escrevê-los.

Fácil de ler.
Código de teste não deve conter nenhum código extra para que o resultado do teste não fique comprometido com este código.

Fácil de executar.

Os testes devem funcionar com o toque de um botão e apresentar seus resultados em uma forma clara e inequívoca.

Rápida de executar.

Testes devem ser executado de modo que eles possam ser executados rapidamente centenas ou milhares de vezes por dia.

Isolado.
Os testes não devem afetar um ao outro. Se a ordem em que os testes são executados mudar, os resultados dos testes não deve mudar.

Combináveis.
Devemos ser capazes de executar qualquer número ou a combinação dos testes em conjunto. Este a natureza do isolamento (o motivo do isolamento dá razão ao combinável).

Há dois confrontos entre estas restrições:

Fácil de aprender a se escrever versus fácil de escrever.
Testes em geral, não exigem toda a flexibilidade de uma linguagem de programação. Muitas ferramentas de teste dispõe linguagem própria, que inclui apenas as características mínimas necessárias para escrever os testes. Os testes resultantes são fáceis de ler e escrever porque eles não têm nenhum ruído para distraí-lo do conteúdo das provas. No entanto, o aprendizado de ainda outra linguagem de programação e um conjunto de ferramentas de programação é inconveniente e confunde o pensamento.

Isolada versus rápido para executar.
Se você quer que os resultados de um teste não tenha qualquer efeito sobre os resultados de outro, cada teste deve criar o ambiente antes de começar a executar e devolver o ambiente ao seu estado original quando ele terminar. Contudo, a criação do ambiente pode levar um longo tempo: por exemplo, se conectar a um banco de dados e inicializa-lo a um estado conhecido, utilizando dados realistas.

PHPUnit tentativas de resolver esses conflitos usando PHP como linguagem de teste. Sometimes the full power of PHP is overkill for writing little straight-line tests, but by using PHP we leverage all the experience and tools programmers already have in place. Às vezes, todo o poder do PHP é overkill para escrever testes linha reta pouco, mas usando PHP Aproveitamos toda a experiência e os programadores de ferramentas já existentes. Since we are trying to convince reluctant testers, lowering the barrier to writing those initial tests is particularly important. Uma vez que estamos a tentar convencer os testadores relutante, abaixando a barreira de escrever os testes iniciais é particularmente importante.

PHPUnit errs on the side of isolation over quick execution. PHPUnit erra no lado de isolamento durante a execução rápida. Isolated tests are valuable because they provide high-quality feedback. Isolado testes são importantes porque fornecem feedback de alta qualidade. You do not get a report with a bunch of test failures, which were really caused because one test at the beginning of the suite failed and left the world messed up for the rest of the tests. Você não receberá um relatório com um monte de falhas nos testes, que foram realmente causadas por um teste no início da suite falhou e deixou o mundo confuso para o resto dos testes. This orientation towards isolated tests encourages designs with a large number of simple objects. Esta orientação para os testes isolados incentiva projetos com um grande número de objetos simples. Each object can be tested quickly in isolation. Cada objeto pode ser rapidamente testados isoladamente. The result is better designs and faster tests. O resultado é melhor os projetos e os testes mais rápidos.

PHPUnit assume que a maioria dos testes é bem-sucedido e não vale a pena relatar a detalhes de testes bem sucedidos. Quando um teste falha, esse fato é digno de nota e relatórios. A grande maioria dos testes deveriam ser bem sucedidos e não vale a pena comentar sobre eles a não ser para contar o número de testes que foram executados. Esta é uma suposição que realmente está construída nas classes de relatório e não no núcleo do PHPUnit. Quando os resultados de um teste são relatados, você vê qwuantos testes foram executados, mas você só ver os detalhes daqueles que falharam.

Os testes devem ser finos (bem pequenos), testar um aspecto de um objeto. Assim, a primeira vez que um teste falhar, a execução do teste pára, e PHPUnit relata a falha. É uma arte testar vários pequenos testes. Testes bem pequenos melhoram o design geral do sistema.

Quando você testar um objeto com PHPUnit, faça apenas através da interface pública do objeto. Teste baseado apenas no comportamento publicamente visível incentiva você a enfrentar e resolver problemas difíceis de design mais cedo, antes dos resultados da má concepção infectar grande parte do sistema.

Capítulo 1. Automatizar testes

Mesmo bons programadores cometem erros. A diferença entre um bom programador e um programador ruim é que o bom programador utiliza testes para detectar seus erros o mais rapidamente possível. Quanto mais cedo você testar maior a sua chance de encontrar um erro e menos custará para e corrigir. Isso explica por que deixar para tester até pouco antes de lançar o software é tão problemático. A maioria dos erros nem são pegos, e o custo de corrigir os que você captura é tão grande que você tem que realizar uma triagem, porque você simplesmente não pode se dar ao luxo de corrigir todos eles.

Teste com PHPUnit não é uma atividade totalmente diferente do que você já deve estar fazendo, é apenas uma maneira diferente de fazê-lo. A diferença está entre o "testar", ou seja, verificar se o seu programa se comporta como esperado, e realizar uma "bateria de testes", fragmentos executáveis de código que automaticamente testam a veracidade das partes, estes fragmentos de código executável são chamados de testes de unidade.

Neste capítulo, iremos partir de simples "print em tela" para um teste completamente automatizado. Imagine que fomos chamados para fezer testes com o array embutido do PHP. Uma funcionalidade para testar é a função count(). Para um array recém-criado, esperamos que a função count() retorne 0 (zero). Depois que adicionar um elemento, count() deve retornar 1 (um). Exemplo 1.1 mostra o que queremos testar.


Exemplo 1.1: operações com matrizes de teste
$fixture = array ( ) ;
// $fixture is expected to be empty.

$fixture [ ] = 'element' ;
// $fixture is expected to contain one element.
?>


Uma maneira simples de verificar se realmente estamos obtendo os resultados que esperamos é imprimir o resultado de count() antes e após a adição do elemento (ver Exemplo 1.2 ). Se conseguirmos 0 e 1 , array e count() se comportam como esperado.


Exemplo 1.2: imprimir usando para testar as operações de matriz
$fixture = array ( ) ;
print count ( $fixture ) . "\n" ;

$fixture [ ] = 'element' ;
print count ( $fixture ) . "\n" ;
?>


Agora, gostaríamos de avançar a partir de testes que exigem interpretação técnica de testes que podem ser executadas automaticamente. No Exemplo 1.3 , nós escrevemos a comparação dos valores reais e esperados que o código de teste imprimo "ok" se os valores são iguais. Se algum dia vier um "não ok" como mensagem, sabemos que algo está errado.


Exemplo 1.3: Comparando os valores previstos e reais para testar as operações de matriz
$fixture = array ( ) ;
print count ( $fixture ) == 0 ? "ok\n" : "não ok\n" ;

$fixture [ ] = 'element' ;
print count ( $fixture ) == 1 ? "ok\n" : "não ok\n" ;
?>


Abaixo temos agora o fator de comparação de reais e os valores esperados para uma função que gera uma exceção quando há uma discrepância ( Exemplo 1,4 ). Isso nos dá duas vantagens: a escrita de testes se torna mais fácil e nós só temos saída quando algo está errado.


Exemplo 1.4: Usando uma função para testar a afirmação de operações com matrizes
$fixture = array ( ) ;
assertTrue ( count ( $fixture ) == 0 ) ;

$fixture [ ] = 'element' ;
assertTrue ( count ( $fixture ) == 1 ) ;

function assertTrue ( $condition )
{
   if ( ! $condition ) {
      throw new Exception ( 'Assertion failed.' ) ;
   }
}
?>


O teste agora é totalmente automatizada. Ao invés de apenas testar como fizemos com a nossa primeira versão, com esta versão, temos um teste automatizado.

O objetivo do uso de testes automatizados é cometer menos erros. Embora seu código ainda não será perfeito, mesmo com testes excelentes, você provavelmente vai ver uma redução drástica nos defeitos asssim que você começar a automatizar seus testes. Os testes automáticos lhe dão justificativa de confiança em seu código. Você pode usar essa confiança para dar mais saltos ousados no design (Refactoring), se dar melhor com seus colegas (Cross-Team Tests), melhorar as relações com seus clientes, e ir para casa todas as noites com a prova de que o sistema está melhor agora do que Foi esta manhã por causa de seus esforços.