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.

Nenhum comentário: