Capítulo 8. Dublês de Testes

Gerard Meszaros introduz o conceito de Dublês de Testes em [Meszaros2007] desta forma:

 

Às vezes é muito difícil testar o sistema sob teste (SST) porque isso depende de outros ambientes que não podem ser usados no ambiente de testes. Isso pode ser porque não estão disponíveis, não retornarão os resultados necessários para o teste, ou porque executá-los causaria efeitos colaterais indesejáveis. Em outros casos, nossa estratégia de testes requer que tenhamos mais controle ou visibilidade do comportamento interno do SST.

Quando estamos escrevendo um teste no qual não podemos (ou decidimos não) usar um ambiente realmente dependente (DOC), podemos substitui-lo por um Dublê de Teste. O Dublê de Teste não precisa se comportar exatamente como o DOC real; apenas precisa fornecer a mesma API como o real, de forma que o SST pense que é o real!

 
 --Gerard Meszaros

O método getMock($nomeClasse) fornecido pelo PHPUnit pode ser usado em um teste para gerar automaticamente um objeto que possa atuar como um dublê de teste para a classe original especificada. Esse objeto de dublê de teste pode ser usado em cada contexto onde um objeto da classe original é esperado.

Por padrão, todos os métodos da classe original são substituídos com uma implementação simulada que apenas retorna NULL (sem chamar o método original). Usando o método will($this->returnValue()), por exemplo, você pode configurar essas implementações simuladas para retornar um valor quando chamadas.

Limitações

Por favor, note que os métodos final, private e static não podem ser esboçados (stubbed) ou falsificados (mocked). Eles são ignorados pela funcionalidade de dublê de teste do PHPUnit e mantêm seus comportamentos originais.

Aviso

Por favor atente para o fato de que a gestão de parâmetros foi mudada. A implementação anterior clona todos os parâmetros de objetos. Isso não permite verificar se o mesmo objeto foi passado para um método ou não. Exemplo 8.14 mostra onde a nova implementação pode ser útil. Exemplo 8.15 mostra como voltar para o comportamento anterior.

Esboços (stubs)

A prática de substituir um objeto por um dublê de teste que (opcionalmente) retorna valores de retorno configurados é chamada de esboçamento. Você pode usar um esboço para "substituir um ambiente real do qual o SST depende de modo que o teste tenha um ponto de controle para as entradas indiretas do SST. Isso permite ao teste forçar o SST através de caminhos que não seriam executáveis de outra forma."

Exemplo 8.2 mostra como esboçar chamadas de método e configurar valores de retorno. Primeiro usamos o método getMock() que é fornecido pela classe PHPUnit_Framework_TestCase para configurar um esboço de objeto que parece com um objeto de SomeClass (Exemplo 8.1). Então usamos a Interface Fluente que o PHPUnit fornece para especificar o comportamento para o esboço. Essencialmente, isso significa que você não precisa criar vários objetos temporários e uni-los depois. Em vez disso, você encadeia chamadas de método como mostrado no exemplo. Isso leva a códigos mais legíveis e "fluentes".

Exemplo 8.1: A classe que queremos esboçar

<?php
class AlgumaClasse
{
public function fazAlgumaCoisa()
{
// Faça algo.
}
}
?>


Exemplo 8.2: Esboçando uma chamada de método para retornar um valor fixo

<?php
require_once 'AlgumaClasse.php';

class EsbocoTest extends PHPUnit_Framework_TestCase
{
public function testEsboco()
{
// Cria um esboço para a classe AlgumaClasse.
$esboco = $this->getMock('AlgumaClasse');

// Configura o esboço.
$esboco->expects($this->any())
->method('fazAlgumaCoisa')
->will($this->returnValue('foo'));

// Chamando $esboco->fazAlgumaCoisa() agora vai retornar 'foo'.
$this->assertEquals('foo', $esboco->fazAlgumaCoisa());
}
}
?>


"Atrás dos bastidores" o PHPUnit automaticamente gera uma nova classe PHP que implementa o comportamento desejado quando um método getMock() é usado. A classe de dublê de teste gerada pode ser configurada através dos argumentos opcionais do método getMock().

  • Por padrão, todos os métodos das classes fornecidas são substituídos com um dublê de teste que apenas retorna NULL a menos que um valor de retorno seja configurado usando will($this->returnValue()), por exemplo.

  • Quando o segundo parâmetro (opcional) é fornecido, apenas os métodos cujos nomes estão no vetor são substituídos com um dublê de teste configurável. O comportamento dos outros métodos não é alterado.

  • O terceiro parâmetro (opcional) pode conter um vetor de parâmetros que é passado para o construtor da classe original (que por padrão não é substituído com a implementação falsa).

  • O quarto parâmetro (opcional) pode ser usado para especificar um nome de classe para a classe de dublê de teste gerada.

  • O quinto parâmetro (opcional) pode ser usado para desabilitar a chamada para o construtor da classe original.

  • O sexto parâmetro (opcional) pode ser usado para desabilitar a chamada para o construtor do clone da classe original.

  • O sétimo parâmetro (opcional) pode ser usado para desabilitar o __autoload() durante a geração da classe de dublê de teste.

Alternativamente, a Mock Builder API pode ser usada para configurar a classe de dublê de teste gerada. Exemplo 8.3 mostra um exemplo. Aqui temos uma lista dos métodos que podem ser usados na interface fluente do Mock Builder:

  • setMethods(vetor $metodos) pode ser chamado no objeto Mock Builder para especificar os métodos que devem ser substituídos com um dublê de teste configurável. O comportamento dos outros métodos não muda.

  • setConstructorArgs(vetor $args) pode ser chamado para fornecer um vetor de parâmetros que é passado ao construtor da classe original (que por padrão não é substituído com uma implementação falsa).

  • setMockClassName($nome) pode ser usado para especificar um nome de classe para a classe de dublê de teste gerada.

  • disableOriginalConstructor() pode ser usado para desabilitar a chamada ao construtor da classe original.

  • disableOriginalClone() pode ser usado para desabilitar a chamada ao construtor do clone da classe original.

  • disableAutoload() pode ser usado para desabilitar o __autoload() durante a geração da classe de dublê de teste.

Exemplo 8.3: Usando a Mock Builder API para configurar a classe de dublê de teste gerada

<?php
require_once 'AlgumaClasse.php';

class EsbocoTest extends PHPUnit_Framework_TestCase
{
public function testEsboco()
{
// Cria um esboço para a classe AlgumaClasse.
$esboco = $this->getMockBuilder('AlgumaClasse')
->disableOriginalConstructor()
->getMock();

// Configura o esboço.
$esboco->expects($this->any())
->method('fazAlgumaCoisa')
->will($this->returnValue('foo'));

// Chamar $esboco->fazAlgumaCoisa() agora vai retornar 'foo'.
$this->assertEquals('foo', $esboco->fazAlgumaCoisa());
}
}
?>


Às vezes você quer retornar um dos argumentos de uma chamada de método (inalterada) como o resultado de uma chamada ao método esboçado. Exemplo 8.4 mostra como você pode conseguir isso usando returnArgument() em vez de returnValue().

Exemplo 8.4: Esboçando uma chamada de método para retornar um dos argumentos

<?php
require_once 'AlgumaClasse.php';

class EsbocoTest extends PHPUnit_Framework_TestCase
{
public function testRetornaArgumentoEsboco()
{
// Cria um esboço para a classe AlgumaClasse.
$esboco = $this->getMock('AlgumaClasse');

// Configura o esboço.
$esboco->expects($this->any())
->method('fazAlgumaCoisa')
->will($this->returnArgument(0));

// $esboco->fazAlgumaCoisa('foo') retorna 'foo'.
$this->assertEquals('foo', $esboco->fazAlgumaCoisa('foo'));

// $esboco->fazAlgumaCoisa('bar') retorna 'bar'.
$this->assertEquals('bar', $esboco->fazAlgumaCoisa('bar'));
}
}
?>


Ao testar uma interface fluente, às vezes é útil fazer um método esboçado retornar uma referência ao objeto esboçado. Exemplo 8.5 mostra como você pode usar returnSelf() para conseguir isso.

Exemplo 8.5: Esboçando uma chamada de método para retornar uma referência ao objeto esboçado

<?php
require_once 'AlgumaClasse.php';

class EsbocoTest extends PHPUnit_Framework_TestCase
{
public function testRetornaEleMesmo()
{
// Cria um esboço para a classe AlgumaClasse.
$esboco = $this->getMock('AlgumaClasse');

// Configura o esboço.
$esboco->expects($this->any())
->method('fazAlgumaCoisa')
->will($this->returnSelf());

// $esboco->fazAlgumaCoisa() retorna $esboco.
$this->assertSame($esboco, $esboco->fazAlgumaCoisa());
}
}
?>


Algumas vezes um método esboçado deveria retornar valores diferentes dependendo de uma lista predefinida de argumentos. Você pode usar returnValueMap() para criar um mapa que associa argumentos com valores de retorno correspondentes. Veja Exemplo 8.6 para ter um exemplo.

Exemplo 8.6: Esboçando uma chamada de método para retornar o valor de um mapa

<?php
require_once 'AlgumaClasse.php';

class EsbocoTest extends PHPUnit_Framework_TestCase
{
public function testRetornaEsbocoMapaValores()
{
// Cria um esboço para a classe AlgumaClasse.
$esboco = $this->getMock('AlgumaClasse');

// Cria um mapa de argumentos para valores retornados.
$mapa = array(
array('a', 'b', 'c', 'd'),
array('e', 'f', 'g', 'h')
);

// Configura o esboço.
$esboco->expects($this->any())
->method('fazAlgumaCoisa')
->will($this->returnValueMap($mapa));

// $esboco->fazAlgumaCoisa() retorna valores diferentes dependendo
// dos argumentos fornecidos.
$this->assertEquals('d', $esboco->fazAlgumaCoisa('a', 'b', 'c'));
$this->assertEquals('h', $esboco->fazAlgumaCoisa('e', 'f', 'g'));
}
}
?>


Quando a chamada ao método esboçado deve retornar um valor calculado em vez de um fixo (veja returnValue()) ou um argumento (inalterado) (veja returnArgument()), você pode usar returnCallback() para que o método esboçado retorne o resultado da função ou método callback. Veja Exemplo 8.7 para ter um exemplo.

Exemplo 8.7: Esboçando uma chamada de método para retornar um valor de um callback

<?php
require_once 'AlgumaClasse.php';

class EsbocoTest extends PHPUnit_Framework_TestCase
{
public function testRetornaEsbocoCallback()
{
// Cria um esboço para a classe AlgumaClasse.
$esboco = $this->getMock('AlgumaClasse');

// Configura o esboço.
$esboco->expects($this->any())
->method('fazAlgumaCoisa')
->will($this->returnCallback('str_rot13'));

// $esboco->fazAlgumaCoisa($argumento) retorna str_rot13($argumento).
$this->assertEquals('fbzrguvat', $esboco->fazAlgumaCoisa('algo'));
}
}
?>


Uma alternativa mais simples para configurar um método callback pode ser especificar uma lista de valores de retorno desejados. Você pode fazer isso com o método onConsecutiveCalls(). Veja Exemplo 8.8 para ter um exemplo.

Exemplo 8.8: Esboçando uma chamada de método para retornar uma lista de valores na ordem especificada

<?php
require_once 'AlgumaClasse.php';

class EsbocoTest extends PHPUnit_Framework_TestCase
{
public function testEsbocoChamadasConsecutivas()
{
// Cria um esboço para a classe AlgumaClasse.
$esboco = $this->getMock('AlgumaClasse');

// Configura o esboço.
$esboco->expects($this->any())
->method('fazAlgumaCoisa')
->will($this->onConsecutiveCalls(2, 3, 5, 7));

// $esboco->fazAlgumaCoisa() retorna um valor diferente de cada vez
$this->assertEquals(2, $esboco->fazAlgumaCoisa());
$this->assertEquals(3, $esboco->fazAlgumaCoisa());
$this->assertEquals(5, $esboco->fazAlgumaCoisa());
}
}
?>


Em vez de retornar um valor, um método esboçado também pode causar uma exceção. Exemplo 8.9 mostra como usar throwException() para fazer isso.

Exemplo 8.9: Esboçando uma chamada de método para lançar uma exceção

<?php
require_once 'AlgumaClasse.php';

class EsbocoTest extends PHPUnit_Framework_TestCase
{
public function testEsbocoLancaExcecao()
{
// Cria um esboço para a classe AlgumaClasse.
$esboco = $this->getMock('AlgumaClasse');

// Configura o esboço.
$esboco->expects($this->any())
->method('fazAlgumaCoisa')
->will($this->throwException(new Exception));

// $esboco->fazAlgumaCoisa() lança uma exceção
$esboco->fazAlgumaCoisa();
}
}
?>


Alternativamente, você mesmo pode escrever um esboço enquanto melhora o design. Recursos amplamente utilizados são acessados através de uma única fachada, então você pode substituir facilmente o recurso pelo esboço. Por exemplo, em vez de ter chamadas diretas ao banco de dados espalhadas pelo código, você tem um único objeto BancoDeDados que implementa a interface IBancoDeDados . Então, você pode criar um esboço de implementação da IBancoDeDados e usá-la em seus testes. Você pode até criar uma opção para executar os testes com o esboço do banco de dados ou com o banco de dados real, então você pode usar seus testes tanto para testes locais durante o desenvolvimento quanto para integração dos testes com o banco de dados real.

Funcionalidades que precisam ser esboçadas tendem a se agrupar no mesmo objeto, aumentando a coesão. Por apresentar a funcionalidade com uma interface única e coerente, você reduz o acoplamento com o resto do sistema.

Objetos Falsos

A prática de substituir um objeto por um dublê de teste que verifica expectativas, por exemplo assertando um método chamado, é conhecido como falsificação (mocking).

Você pode usar um objeto falso "como um ponto de observação que é usado para verificar as saídas indiretas do SST durante seu exercício. Tipicamente, o objeto falso também inclui a funcionalidade de um esboço de teste que deve retornar valores para o SST se ainda não tiver falhado nos testes, mas a ênfase está na verificação das saídas indiretas. Portanto, um objeto falso é muito mais que apenas um esboço de testes mais asserções; é utilizado de uma forma fundamentalmente diferente".

Aqui está um exemplo: suponha que queiramos testar se o método correto, update() em nosso exemplo, é chamado em um objeto que observa outro objeto. Exemplo 8.10 mostra o código para as classes Sujeito e Observador que são parte do Sistema Sob Teste (SST).

Exemplo 8.10: As classes Sujeito e Observador que são parte do Sistema Sob Teste (SST)

<?php
class Sujeito
{
protected $observadores = array();

public function anexar(Observador $observador)
{
$this->observadors[] = $observador;
}

public function fazAlgumaCoisa()
{
// Faça algo.
// ...

// Notifica aos observadores que fizemos algo.
$this->notify('algo');
}

public function fazAlgumaCoisaRuim()
{
foreach ($this->observadores as $observador) {
$observador->reportError(42, 'Alguma coisa ruim aconteceu.', $this);
}
}

protected function notify($argumento)
{
foreach ($this->observadores as $observador) {
$observador->update($argumento);
}
}

// Outros métodos.
}

class Observador
{
public function update($argumento)
{
// Faça algo.
}

public function realatarErro($codigoErro, $mensagemErro, Sujeito $sujeito)
{
// Faça algo.
}

// Outros métodos.
}
?>


Exemplo 8.11 mostra como usar um objeto falso para testar a interação entre os objetos Sujeito e Observador.

Primeiro usamos o método getMock() que é fornecido pela classe PHPUnit_Framework_TestCase para configurar um objeto falso para ser o Observer. Já que fornecemos um vetor como segundo parâmetro (opcional) para o método getMock() apenas o método update() da classe Observador é substituído por uma implementação falsificada.

Exemplo 8.11: Testando se um método é chamado uma vez e com o argumento especificado

<?php
class SujeitoTest extends PHPUnit_Framework_TestCase
{
public function testObservadoresEstaoAtualizados()
{
// Cria uma falsificação para a classe Observador,
// apenas falsificando o método atualizar().
$observador = $this->getMock('Observador', array('atualizar'));

// Configura a expectativa para o método atualizar()
// para ser chamado apenas uma vez e com a string 'algo'
// como seu parâmetro.
$observador->expects($this->once())
->method('atualizar')
->with($this->equalTo('algo'));

// Cria um objeto Sujeito e anexa a ele o objeto
// Observador falso.
$sujeito = new Subject;
$sujeito->attach($observador);

// Chama o método fazAlgumaCoisa() no objeto $sujeito
// no qual esperamos chamar o método atualizar()
// do objeto falso Observador, com a string 'algo'.
$sujeito->fazAlgumaCoisa();
}
}
?>


O método with() pode receber qualquer número de argumentos, correspondendo ao número de parâmetros sendo falsos. Você pode especificar restrições mais avançadas do que uma simples igualdade no argumento do método.

Exemplo 8.12: Testando se um método é chamado com um número de argumentos restringidos de formas diferentes

<?php
class SujeitoTest extends PHPUnit_Framework_TestCase
{
public function testErroRelatado()
{
// Cria uma falsificação para a classe Observador,
// falsificando o método reportError()
$observador = $this->getMock('Observador', array('relatarErro'));

$observador->expects($this->once())
->method('relatarErro')
->with($this->greaterThan(0),
$this->stringContains('Algo'),
$this->anything());

$sujeito = new Sujeito;
$sujeito->attach($observador);

// O método fazAlgumaCoisaRuim() deveria relatar um erro
// ao observador via método reportError()
$sujeito->fazAlgumaCoisaRuim();
}
}
?>


Tabela 2.3 mostra as restrições que podem ser aplicadas aos argumentos do método e Tabela 8.1 mostra os equiparadores que estão disponíveis para especificar o número de invocações.

Tabela 8.1. Equiparadores

EquiparadorSignificado
PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount any()Retorna um equiparador que corresponde quando o método que é avaliado for executado zero ou mais vezes.
PHPUnit_Framework_MockObject_Matcher_InvokedCount never()Retorna um equiparador que corresponde quando o método que é avaliado nunca for executado.
PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce atLeastOnce()Retorna um equiparador que corresponde quando o método que é avaliado for executado pelo menos uma vez.
PHPUnit_Framework_MockObject_Matcher_InvokedCount once()Retorna um equiparador que corresponde quando o método que é avaliado for executado exatamente uma vez.
PHPUnit_Framework_MockObject_Matcher_InvokedCount exactly(int $conta)Retorna um equiparador que corresponde quando o método que é avaliado for executado exatamente $conta vezes.
PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex at(int $indice)Retorna um equiparador que corresponde quando o método que é avaliado for invocado no $indice fornecido.


O método getMockForAbstractClass() retorna um objeto falso para uma classe abstrata. Todos os métodos abstratos da classe abstrata fornecida são falsos. Isso permite testar os métodos concretos de uma classe abstrata.

Exemplo 8.13: Testando os métodos concretos de uma classe abstrata

<?php
abstract class ClasseAbstrata
{
public function metodoConcreto()
{
return $this->metodoAbstrato();
}

public abstract function metodoAbstrato();
}

class ClasseAbstrataTest extends PHPUnit_Framework_TestCase
{
public function testMetodoConcreto()
{
$esboco = $this->getMockForAbstractClass('ClasseAbstrata');
$esboco->expects($this->any())
->method('metodoAbstrato')
->will($this->returnValue(TRUE));

$this->assertTrue($esboco->metodoConcreto());
}
}
?>


Exemplo 8.14: Testando se um método é chamado uma vez e com o objeto idêntico ao que foi passado

<?php
class FooTest extends PHPUnit_Framework_TestCase
{
public function testObjetoIdenticoPassado()
{
$objetoEsperado = new stdClass;

$falso = $this->getMock('stdClass', array('foo'));
$falso->expects($this->once())
->method('foo')
->with($this->identicalTo($objetoEsperado));

$falso->foo($objetoEsperado);
}
}
?>


Exemplo 8.15: Criando um objeto falso com clonagem de parâmetros ativada

<?php
class FooTest extends PHPUnit_Framework_TestCase
{
public function testObjetoIdenticoPassado()
{
$argumentosClonados = true;

$falso = $this->getMock(
'stdClass',
array(),
array(),
'',
FALSE,
TRUE,
TRUE,
$argumentosClonados
);

// ou usando o mock builder
$falso = $this->getMockBuilder('stdClass')->enableArgumentCloning()->getMock();

// agora você falsifica seus parâmetros de clones de modo que a restrição identicalTo vá falhar.
}
}
?>


Esboçando e Falsificando Serviços Web

Quando sua aplicação interage com um serviço web você quer testá-lo sem realmente interagir com o serviço web. Para tornar mais fáceis o esboço e falsificação dos serviços web, o getMockFromWsdl() pode ser usado da mesma forma que o getMock() (vide acima). A única diferença é que getMockFromWsdl() retorna um esboço ou falsificação baseado em uma descrição de um serviço web em WSDL e getMock() retorna um esboço ou falsificação baseado em uma classe ou interface PHP.

Exemplo 8.16 mostra como getMockFromWsdl() pode ser usado para esboçar, por exemplo, o erviço web descrito em GoogleSearch.wsdl.

Exemplo 8.16: Esboçando um serviço web

<?php
class GoogleTest extends PHPUnit_Framework_TestCase
{
public function testSearch()
{
$googleSearch = $this->getMockFromWsdl(
'GoogleSearch.wsdl', 'GoogleSearch'
);

$directoryCategory = new StdClass;
$directoryCategory->fullViewableName = '';
$directoryCategory->specialEncoding = '';

$elemento = new StdClass;
$elemento->summary = '';
$elemento->URL = 'http://www.phpunit.de/';
$elemento->snippet = '...';
$elemento->title = '<b>PHPUnit</b>';
$elemento->cachedSize = '11k';
$elemento->relatedInformationPresent = TRUE;
$elemento->hostName = 'www.phpunit.de';
$elemento->directoryCategory = $directoryCategory;
$elemento->directoryTitle = '';

$resultado = new StdClass;
$resultado->documentFiltering = FALSE;
$resultado->searchComments = '';
$resultado->estimatedTotalResultsCount = 3.9000;
$resultado->estimateIsExact = FALSE;
$resultado->resultElements = array($elemento);
$resultado->searchQuery = 'PHPUnit';
$resultado->startIndex = 1;
$resultado->endIndex = 1;
$resultado->searchTips = '';
$resultado->directoryCategories = array();
$resultado->searchTime = 0.248822;

$googleSearch->expects($this->any())
->method('doGoogleSearch')
->will($this->returnValue($resultado));

/**
* $googleSearch->doGoogleSearch() agora retornará um resultado esboçado
* e o método doGoogleSearch() do serviço web não será invocado.
*/
$this->assertEquals(
$result,
$googleSearch->doGoogleSearch(
'00000000000000000000000000000000',
'PHPUnit',
0,
1,
FALSE,
'',
FALSE,
'',
'',
''
)
);
}
}
?>


Esboçando o Sistema de Arquivos

vfsStream é um stream wrapper para um sistema de arquivos virtual que pode ser útil em testes unitários para falsificar um sistema de arquivos real.

Para instalar o vfsStram, o canal PEAR (pear.bovigo.org) que é usado para esta distribuição precisa ser registrado com o ambiente PEAR local.

pear channel-discover pear.bovigo.org

Isso só precisa ser feito uma vez. Agora o Instalador PEAR pode ser usado para instalar o vfsStream:

pear install bovigo/vfsStream-beta

Exemplo 8.17 mostra a classe que interage com o sistema de arquivos.

Exemplo 8.17: Uma classe que interage com um sistema de arquivos

<?php
class Exemplo
{
protected $id;
protected $diretorio;

public function __construct($id)
{
$this->id = $id;
}

public function setDiretorio($diretorio)
{
$this->diretorio = $diretorio . SEPARADOR_DE_DIRETORIO . $this->id;

if (!file_exists($this->diretorio)) {
mkdir($this->diretorio, 0700, TRUE);
}
}
}?>


Sem um sistema de arquivos virtual como o vfsStream não poderíamos testar o método setDirectory() isolado de influências externas (veja Exemplo 8.18).

Exemplo 8.18: Testando uma classe que interage com o sistema de arquivos

<?php
require_once 'Exemplo.php';

class ExemploTest extends PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (file_exists(dirname(__FILE__) . '/id')) {
rmdir(dirname(__FILE__) . '/id');
}
}

public function testDiretorioFoiCriado()
{
$exemplo = new Exemplo('id');
$this->assertFalse(file_exists(dirname(__FILE__) . '/id'));

$exemplo->setDiretorio(dirname(__FILE__));
$this->assertTrue(file_exists(dirname(__FILE__) . '/id'));
}

protected function tearDown()
{
if (file_exists(dirname(__FILE__) . '/id')) {
rmdir(dirname(__FILE__) . '/id');
}
}
}
?>


A abordagem acima tem várias desvantagens:

  • Assim como um recurso externo, podem haver problemas intermitentes com o sistema de arquivos. Isso deixa os testes com os quais interage esquisitos.

  • Nos métodos setUp() e tearDown() temos que assegurar que o diretório não existe antes e depois do teste.

  • Quando a execução do teste termina antes do método tearDown() ser invocado, o diretório permanece no sistema de arquivos.

Exemplo 8.19 mostra como o vfsStream pode ser usado para falsificar o sistema de arquivos em um teste para uma classe que interage com o sistema de arquivos.

Exemplo 8.19: Falsificando o sistema de arquivos em um teste para a classe que interage com o sistema de arquivos

<?php
require_once 'vfsStream/vfsStream.php';
require_once 'Exemplo.php';

class ExemploTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
vfsStreamWrapper::register();
vfsStreamWrapper::setRoot(new vfsStreamDirectory('diretorioExemplo'));
}

public function testDiretorioFoiCriado()
{
$exemplo = new Exemplo('id');
$this->assertFalse(vfsStreamWrapper::getRoot()->hasChild('id'));

$exemplo->setDirectory(vfsStream::url('diretorioExemplo'));
$this->assertTrue(vfsStreamWrapper::getRoot()->hasChild('id'));
}
}
?>


Isso tem várias vantagens:

  • O próprio teste fica mais conciso.

  • O vfsStream concede ao desenvolvedor de testes controle total sobre a aparência do ambiente do sistema de arquivos para o código testado.

  • Já que as operações do sistema de arquivos não operam mais no sistema de arquivos real, operações de limpeza em um método tearDown() não são mais exigidas.