testar o valor de retorno de um método que desencadeia um erro com PHPUnit
-
22-07-2019 - |
Pergunta
Esta questão é específico para usando PHPUnit.
PHPUnit converte automaticamente erros php para exceções. Existe uma maneira de testar o valor de retorno de um método que acontece para acionar um erro de php (ou built-in erros ou usuário erros gerados via trigger_error )?
Exemplo de código para teste:
function load_file ($file)
{
if (! file_exists($file)) {
trigger_error("file {$file} does not exist", E_USER_WARNING);
return false;
}
return file_get_contents($file);
}
Este é o tipo de teste que eu quero escrever:
public function testLoadFile ()
{
$this->assertFalse(load_file('/some/non-existent/file'));
}
O problema que estou tendo é que o erro desencadeada faz com que meu teste de unidade falhe (como deveria). Mas se eu tentar pegá-lo, ou definir uma exceção esperada qualquer código que após o erro é acionado nunca executa então eu não tenho nenhuma maneira de testar o valor de retorno do método.
Este exemplo não funciona:
public function testLoadFile ()
{
$this->setExpectedException('Exception');
$result = load_file('/some/non-existent/file');
// code after this point never gets executed
$this->assertFalse($result);
}
Alguma idéia de como eu poderia conseguir isso?
Solução
Não há nenhuma maneira de fazer isso dentro de um teste de unidade. É possível se você quebrar-se testar o valor de retorno, eo aviso em dois testes diferentes.
manipulador de erro do PHPUnit pega erros PHP e avisos e os converte em Exceções - que por definição pára a execução do programa. A função que você está testando nunca retorna em tudo. Você pode, no entanto, desativar temporariamente a conversão de erros em exceções, mesmo em tempo de execução.
Este é provavelmente mais fácil com um exemplo, então, aqui está o que os dois testes deve ser parecido:
public function testLoadFileTriggersErrorWhenFileNotFound()
{
$this->setExpectedException('PHPUnit_Framework_Error_Warning'); // Or whichever exception it is
$result = load_file('/some/non-existent/file');
}
public function testLoadFileRetunsFalseWhenFileNotFound()
{
PHPUnit_Framework_Error_Warning::$enabled = FALSE;
$result = load_file('/some/non-existent/file');
$this->assertFalse($result);
}
Isto também tem a vantagem adicional de fazer seus testes mais clara, mais limpa e auto documentar.
Re: Comentário: Essa é uma ótima pergunta, e eu não tinha idéia até que eu corri um par de testes. É como se ele irá não restaurar o valor padrão / original, pelo menos a partir de PHPUnit 3.3.17 (a versão estável atual agora).
Então, eu seria realmente alterar a acima para olhar como assim:
public function testLoadFileRetunsFalseWhenFileNotFound()
{
$warningEnabledOrig = PHPUnit_Framework_Error_Warning::$enabled;
PHPUnit_Framework_Error_Warning::$enabled = false;
$result = load_file('/some/non-existent/file');
$this->assertFalse($result);
PHPUnit_Framework_Error_Warning::$enabled = $warningEnabledOrig;
}
Re: Segundo comentário:
Isso não é totalmente verdade. Eu estou olhando para manipulador de erro do PHPUnit, e funciona da seguinte forma:
- Se for um
E_WARNING
,PHPUnit_Framework_Error_Warning
uso como uma classe de exceção. - Se for um
E_NOTICE
ou erroE_STRICT
, usoPHPUnit_Framework_Error_Notice
- Else, o uso
PHPUnit_Framework_Error
como a classe de exceção.
Então, sim, erros da E_USER_*
não são transformados em classe * _Warning ou * _Notice do PHPUnit, eles ainda são transformados em uma exceção PHPUnit_Framework_Error
genérico.
Outras Pensamentos
Enquanto isso depende exatamente da forma como a função é usada, eu provavelmente mudar para lançar uma exceção real em vez de provocar um erro, se fosse comigo. Sim, isso mudaria o fluxo lógico do método, eo código que usa o método ... agora a execução não parar quando ele não consegue ler um ficheiro. Mas isso é até você para decidir se o arquivo solicitado não existente é verdadeiramente excepcional comportamento. I tendem a usar exceções muito mais do que erros / avisos / notificações, porque eles são mais fáceis de manusear, testar e trabalho em seu fluxo da aplicação. Eu costumo reservar os avisos para coisas como chamadas de método depreciados, etc.
Outras dicas
Use um arquivo de configuração phpunit.xml
e desativar o aviso / aviso / erro para conversão de exceção. Mais detalhes no manual. É basicamente algo como isto:
<phpunit convertErrorsToExceptions="false"
convertNoticesToExceptions="false"
convertWarningsToExceptions="false">
</phpunit>
Em vez de esperar uma "Exception
" genérico, o que acontece com esperando um "PHPUnit_Framework_Error
"?
Algo como isso pode fazer:
/**
* @expectedException PHPUnit_Framework_Error
*/
public function testFailingInclude()
{
include 'not_existing_file.php';
}
O que, suponho, também pode ser escrita como:
public function testLoadFile ()
{
$this->setExpectedException('PHPUnit_Framework_Error');
$result = load_file('/some/non-existent/file');
// code after this point never gets executed
$this->assertFalse($result);
}
Para mais informações, consulte Erros Testing PHP
Especialmente, ele diz (citando):
PHPUnit_Framework_Error_Notice
ePHPUnit_Framework_Error_Warning
representam PHP percebe e alerta, respectivamente.
Olhando para o arquivo /usr/share/php/PHPUnit/TextUI/TestRunner.php eu tenho no meu sistema, eu vejo esta (linha 198 e seguintes) :
if (!$arguments['convertNoticesToExceptions']) {
PHPUnit_Framework_Error_Notice::$enabled = FALSE;
}
if (!$arguments['convertWarningsToExceptions']) {
PHPUnit_Framework_Error_Warning::$enabled = FALSE;
}
Então, talvez você vai ter que passar algum tipo de parâmetro para ativar esse comportamento? Mas parece ser ativado por padrão ...
Na verdade, há uma maneira de testar tanto o valor de retorno e a exceção lançada (neste caso, um erro convertido por PHPUnit).
Você apenas tem que fazer o seguinte:
public function testLoadFileTriggersErrorWhenFileNotFound()
{
$this->assertFalse(@load_file('/some/non-existent/file'));
$this->setExpectedException('PHPUnit_Framework_Error_Warning'); // Or whichever exception it is
load_file('/some/non-existent/file');
}
Observe que para testar o valor de retorno que você tem que usar o operador de supressão de erro na chamada de função (o @
antes o nome da função). Desta forma, nenhuma exceção será lançada e a execução continuará. Você então tem que definir a exceção esperada, como de costume para testar o erro.
O que você não pode fazer é testar várias exceções dentro de uma unidade de teste.