Capítulo 15. Estendendo o PHPUnit

O PHPUnit pode ser estendido de várias formas para facilitar a escrita de testes e personalizar as respostas que você recebe ao executar os testes. Aqui estão pontos de partida comuns para estender o PHPUnit.

Subclasse PHPUnit_Framework_TestCase

Escreva asserções personalizadas e métodos utilitários em uma subclasse abstrata do PHPUnit_Framework_TestCase e derive suas classes de caso de teste dessa classe. Essa é uma das formas mais fáceis de estender o PHPUnit.

Escreva asserções personalizadas

Ao escrever asserções personalizadas a melhor prática é seguir a mesma forma que as asserções do próprio PHPUnit são implementadas. Como você pode ver no Exemplo 15.1, o método assertTrue() é apenas um empacotador em torno dos métodos isTrue() e assertThat(): isTrue() cria um objeto comparador que é passado para assertThat() para avaliação.

Exemplo 15.1: Os métodos assertTrue() e isTrue() da classe PHPUnitFramework_Assert

<?php
abstract class PHPUnit_Framework_Assert
{
    // ...

    /**
     * Assevera que uma condição é verdadeira (true).
     *
     * @param  boolean $condition
     * @param  string  $message
     * @throws PHPUnit_Framework_AssertionFailedError
     */
    public static function assertTrue($condition, $message = '')
    {
        self::assertThat($condition, self::isTrue(), $message);
    }

    // ...

    /**
     * Retorna um objeto comparador PHPUnit_Framework_Constraint_IsTrue.
     *
     * @return PHPUnit_Framework_Constraint_IsTrue
     * @since Método disponível desde a versão 3.3.0
     */
    public static function isTrue()
    {
        return new PHPUnit_Framework_Constraint_IsTrue;
    }

    // ...
}?>


O Exemplo 15.2 mostra como PHPUnit_Framework_Constraint_IsTrue estende a classe base abstrata para objetos comparadores (ou restritores), PHPUnit_Framework_Constraint.

Exemplo 15.2: A classe PHPUnit_Framework_Constraint_IsTrue

<?php
class PHPUnit_Framework_Constraint_IsTrue extends PHPUnit_Framework_Constraint
{
    /**
     * Avalia a restrição para o parâmetro $other. Retorna TRUE se a
     * restrição é confirmada, FALSE caso contrário.
     *
     * @param mixed $other Valor ou objeto a avaliar.
     * @return bool
     */
    public function matches($other)
    {
        return $other === TRUE;
    }

    /**
     * Retorna uma representação string da restrição.
     *
     * @return string
     */
    public function toString()
    {
        return 'is true';
    }
}?>


O esforço de implementar os métodos assertTrue() e isTrue() assim como a classe PHPUnit_Framework_Constraint_IsTrue rende o benefício de que assertThat() automaticamente cuida de avaliar a asserção e escriturar tarefas como contá-las para estatísticas. Além disso, o método isTrue() pode ser usado como um comparador ao configurar objetos falsificados.

Implementando PHPUnit_Framework_TestListener

O Exemplo 15.3 mostra uma implementação simples da interface PHPUnit_Framework_TestListener.

Exemplo 15.3: Um simples ouvinte de teste

<?php
class SimpleTestListener implements PHPUnit_Framework_TestListener
{
    public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        printf("Error while running test '%s'.\n", $test->getName());
    }

    public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
    {
        printf("Test '%s' failed.\n", $test->getName());
    }

    public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        printf("Test '%s' is incomplete.\n", $test->getName());
    }

    public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        printf("Test '%s' is deemed risky.\n", $test->getName());
    }

    public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
    {
        printf("Test '%s' has been skipped.\n", $test->getName());
    }

    public function startTest(PHPUnit_Framework_Test $test)
    {
        printf("Test '%s' started.\n", $test->getName());
    }

    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        printf("Test '%s' ended.\n", $test->getName());
    }

    public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        printf("TestSuite '%s' started.\n", $suite->getName());
    }

    public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
    {
        printf("TestSuite '%s' ended.\n", $suite->getName());
    }
}
?>


Exemplo 15.4 mostra como a subclasse da classe abstrata PHPUnit_Framework_BaseTestListener, que permite que você especifique apenas os métodos de interface que são interessantes para seu caso de uso, ao fornecer implementações vazias para todos os outros.

Exemplo 15.4: Usando o ouvinte de teste base

<?php
class ShortTestListener extends PHPUnit_Framework_BaseTestListener
{
    public function endTest(PHPUnit_Framework_Test $test, $time)
    {
        printf("Test '%s' ended.\n", $test->getName());
    }
}
?>


Em “Ouvintes de Teste” você pode ver como configurar o PHPUnit para anexar seu ouvinte de teste para a execução do teste.

Subclasse PHPUnit_Extensions_TestDecorator

Você pode envolver casos de teste ou suítes de teste em uma subclasse de PHPUnit_Extensions_TestDecorator e usar o padrão de projeto Decorador para realizar algumas ações antes e depois da execução do teste.

O PHPUnit navega com um decorador de teste concreto: PHPUnit_Extensions_RepeatedTest. Ele é usado para executar um um teste repetidamente e apenas o conta como bem-sucedido se todas as iterações forem bem-sucedidas.

O Exemplo 15.5 mostra uma versão resumida do decorador de teste PHPUnit_Extensions_RepeatedTest que ilustra como escrever seus próprios decoradores de teste.

Exemplo 15.5: O Decorador RepeatedTest

<?php
require_once 'PHPUnit/Extensions/TestDecorator.php';

class PHPUnit_Extensions_RepeatedTest extends PHPUnit_Extensions_TestDecorator
{
    private $timesRepeat = 1;

    public function __construct(PHPUnit_Framework_Test $test, $timesRepeat = 1)
    {
        parent::__construct($test);

        if (is_integer($timesRepeat) &&
            $timesRepeat >= 0) {
            $this->timesRepeat = $timesRepeat;
        }
    }

    public function count()
    {
        return $this->timesRepeat * $this->test->count();
    }

    public function run(PHPUnit_Framework_TestResult $result = NULL)
    {
        if ($result === NULL) {
            $result = $this->createResult();
        }

        for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) {
            $this->test->run($result);
        }

        return $result;
    }
}
?>


Implementando PHPUnit_Framework_Test

A interface PHPUnit_Framework_Test é limitada e fácil de implementar. Você pode escrever uma implementação do PHPUnit_Framework_Test que é mais simples que PHPUnit_Framework_TestCase e que executa testes guiados por dados, por exemplo.

O Exemplo 15.6 mostra uma classe de caso de teste guiado por dados que compara valores de um arquivo com Valores Separados por Vírgulas (CSV). Cada linha de tal arquivo parece com foo;bar, onde o primeiro valor é o qual esperamos e o segundo valor é o real.

Exemplo 15.6: Um teste guiado por dados

<?php
class DataDrivenTest implements PHPUnit_Framework_Test
{
    private $lines;

    public function __construct($dataFile)
    {
        $this->lines = file($dataFile);
    }

    public function count()
    {
        return 1;
    }

    public function run(PHPUnit_Framework_TestResult $result = NULL)
    {
        if ($result === NULL) {
            $result = new PHPUnit_Framework_TestResult;
        }

        foreach ($this->lines as $line) {
            $result->startTest($this);
            PHP_Timer::start();
            $stopTime = NULL;

            list($expected, $actual) = explode(';', $line);

            try {
                PHPUnit_Framework_Assert::assertEquals(
                  trim($expected), trim($actual)
                );
            }

            catch (PHPUnit_Framework_AssertionFailedError $e) {
                $stopTime = PHP_Timer::stop();
                $result->addFailure($this, $e, $stopTime);
            }

            catch (Exception $e) {
                $stopTime = PHP_Timer::stop();
                $result->addError($this, $e, $stopTime);
            }

            if ($stopTime === NULL) {
                $stopTime = PHP_Timer::stop();
            }

            $result->endTest($this, $stopTime);
        }

        return $result;
    }
}

$test = new DataDrivenTest('data_file.csv');
$result = PHPUnit_TextUI_TestRunner::run($test);
?>
PHPUnit 4.7.0 by Sebastian Bergmann and contributors.

.F

Time: 0 seconds

There was 1 failure:

1) DataDrivenTest
Failed asserting that two strings are equal.
expected string <bar>
difference      <  x>
got string      <baz>
/home/sb/DataDrivenTest.php:32
/home/sb/DataDrivenTest.php:53

FAILURES!
Tests: 2, Failures: 1.


Por favor, abra um chamado no GitHub para sugerir melhorias para esta página. Obrigado!