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 em 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
{
// ...

/**
* Asserta que uma condição é verdade.
*
* @param boolean $condicao
* @param string $menssagem
* @throws PHPUnit_Framework_AssertionFailedError
*/
public static function assertTrue($condicao, $mensagem = '')
{
self::assertThat($condicao, self::isTrue(), $mensagem);
}

// ...

/**
* Retorna um objeto equiparador 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;
}

// ...
}?>


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 $outro. Retorna TRUE se a
* restrição é confirmada, FALSE caso contrário.
*
* @param misto $outro Valor ou objeto a avaliar.
* @return bool
*/
public function matches($outro)
{
return $outro === TRUE;
}

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


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 falsos.

Implementando PHPUnit_Framework_TestListener

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

Exemplo 15.3: Um simples ouvinte de teste

<?php
class SimplesOuvinteDeTest implements PHPUnit_Framework_TestListener
{
public function addError(PHPUnit_Framework_Test $teste, Exception $e, $tempo)
{
printf("Erro ao executar o teste '%s'.\n", $teste->getName());
}

public function addFailure(PHPUnit_Framework_Test $teste, PHPUnit_Framework_AssertionFailedError $e, $tempo)
{
printf("O Teste '%s' falhou.\n", $teste->getName());
}

public function addIncompleteTest(PHPUnit_Framework_Test $teste, Exception $e, $tempo)
{
printf("O Teste '%s' está incompleto.\n", $teste->getName());
}

public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
{
printf("O Teste '%s' é arriscado.\n", $test->getName());
}

public function addSkippedTest(PHPUnit_Framework_Test $teste, Exception $e, $tempo)
{
printf("O Teste '%s' foi pulado.\n", $teste->getName());
}

public function startTest(PHPUnit_Framework_Test $teste)
{
printf("O Teste '%s' iniciou.\n", $teste->getName());
}

public function endTest(PHPUnit_Framework_Test $teste, $tempo)
{
printf("O Teste '%s' terminou.\n", $teste->getName());
}

public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
{
printf("A Suíte de Testes '%s' iniciou.\n", $suite->getName());
}

public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
{
printf("A Suíte de Testes '%s' terminou.\n", $suite->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

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

O PHPUnit navega com dois decoradores de teste concretos: PHPUnit_Extensions_RepeatedTest e PHPUnit_Extensions_TestSetup. O formador é usado para executar um teste repetidamente e apenas o conta como bem-sucedido se todas as iterações forem bem-sucedidas. O último foi discutido em Capítulo 4.

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

Exemplo 15.4: O Decorador RepeatedTest

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

class PHPUnit_Extensions_RepeatedTest extends PHPUnit_Extensions_TestDecorator
{
private $repetirVezes = 1;

public function __construct(PHPUnit_Framework_Test $teste, $repetirVezes = 1)
{
parent::__construct($teste);

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

public function count()
{
return $this->repetirVezes * $this->teste->count();
}

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

for ($i = 0; $i < $this->repetirVezes && !$resultado->shouldStop(); $i++) {
$this->teste->run($resultado);
}

return $resultado;
}
}
?>


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 data-driven tests, por exemplo.

Exemplo 15.5 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 é o real.

Exemplo 15.5: Um teste guiado por dados

<?php
class GuiadoPorDadosTest implements PHPUnit_Framework_Test
{
private $linhas;

public function __construct($arquivoDados)
{
$this->linhas = file($arquivoDados);
}

public function count()
{
return 1;
}

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

foreach ($this->linhas as $linha) {
$resultado->startTest($this);
PHP_Timer::start();
$stopTime = NULL;

list($esperado, $real) = explode(';', $linha);

try {
PHPUnit_Framework_Assert::assertEquals(
trim($esperado), trim($real)
);
}

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

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

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

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

return $resultado;
}
}

$teste = new GuiadoPorDadosTest('arquivo_dados.csv');
$resultado = PHPUnit_TextUI_TestRunner::run($teste);
?>
PHPUnit 4.2.0 by Sebastian Bergmann.

.F

Time: 0 seconds

There was 1 failure:

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

FAILURES!
Tests: 2, Failures: 1.