Prev Next

Capítulo 14. Análise de Cobertura de Código

 

A beleza de testar não se encontra no esforço, mas na eficiência.

Saber o que deveria ser testado é lindo, e saber o que está sendo testado é lindo.

 
  --Murali Nandigama

Neste capítulo você aprenderá tudo sobre a funcionalidade de cobertura de código do PHPUnit que lhe dará uma perspicácia sobre quais partes do código de produção são executadas quando os testes são executados. Isso ajuda a responder questões como:

  • Como você descobre o código que ainda não foi testado -- ou, em outras palavras, ainda não foi coberto por um teste?

  • Como você mede o quanto os testes estão completos?

Um exemplo sobre o quê podem significar as estatísticas de cobertura de código é que se existir um método com 100 linhas de código, e apenas 75 dessas linhas são realmente executadas quando os testes são executados, então considera-se que o método tem uma cobertura de código de 75 porcento.

A funcionalidade de cobertura de código do PHPUnit faz uso do componente PHP_CodeCoverage que por sua vez aproveita a funcionalidade da cobertura de declarações fornecida pela extensão Xdebug para PHP.

Deixe-nos gerar um relatório de cobertura de código para a classe ContaBancaria de Exemplo 12.3.

phpunit --coverage-html ./report ContaBancariaTest
PHPUnit 3.7.0 by Sebastian Bergmann.

...

Time: 0 seconds

OK (3 tests, 3 assertions)

Generating report, this may take a moment.

Figura 14.1 mostra um resumo do relatório de Cobertura de Código. Linhas de código que foram executadas enquanto os testes executavam estão destacadas em verde, linhas de código que são executáveis mas não foram executadas estão destacadas em vermelho, e o "código morto" está destacado em cinza. O número à esquerda da linha de código atual indica quantos testes cobrem aquela linha.

Figura 14.1. Cobertura de Código para setSaldo()

Cobertura de Código para setSaldo()

Clicando no número da linha de uma linha coberta abrirá um painel (veja Figura 14.2) que mostra os casos de testes que cobrem esta linha.

Figura 14.2. Painel com informações sobre cobertura de testes

Painel com informações sobre cobertura de testes

O relatório de cobertura de código para nosso exemplo ContaBancaria mostra que não temos quaisquer testes ainda que chamem os métodos setBalance(), depositMoney(), e withdrawMoney() com valores legais. Exemplo 14.1 mostra um teste que pode ser adicionado à classe de caso de teste ContaBancariaTest para cobrir completamente a classe ContaBancaria.

Exemplo 14.1: Teste perdido para conseguir completa cobertura de código

<?php
require_once 'ContaBancaria.php';

class ContaBancariaTest extends PHPUnit_Framework_TestCase
{
// ...

public function testDepositarSacarDinheiro()
{
$this->assertEquals(0, $this->cb->getSaldo());
$this->cb->depositarDinheiro(1);
$this->assertEquals(1, $this->cb->getSaldo());
$this->cb->sacarDinheiro(1);
$this->assertEquals(0, $this->cb->getSaldo());
}
}
?>

Figura 14.3 mostra a cobertura de código para o método setSaldo() com o teste adicional.

Figura 14.3. Cobertura de Código para setSaldo() com teste adicional

Cobertura de Código para setSaldo() com teste adicional

Especificando métodos cobertos

A anotação @covers (veja Tabela B.1) pode ser usada em um código de teste para especificar qual(is) método(s) um método de teste quer testar. Se fornecido, apenas a informação de cobertura de código para o(s) método(s) especificado(s) será considerada. Exemplo 14.2 mostra um exemplo.

Exemplo 14.2: Testes que especificam quais métodos querem cobrir

<?php
require_once 'ContaBancaria.php';

class ContaBancariaTest extends PHPUnit_Framework_TestCase
{
protected $cb;

protected function setUp()
{
$this->cb = new ContaBancaria;
}

/**
* @covers ContaBancaria::getSaldo
*/
public function testSaldoInicialEhZero()
{
$this->assertEquals(0, $this->cb->getSaldo());
}

/**
* @covers ContaBancaria::sacarDinheiro
*/
public function testSaldoNaoPodeFicarNegativo()
{
try {
$this->cb->sacarDinheiro(1);
}

catch (ExcecaoContaBancaria $e) {
$this->assertEquals(0, $this->cb->getSaldo());

return;
}

$this->fail();
}

/**
* @covers ContaBancaria::depositarDinheiro
*/
public function testBalanceCannotBecomeNegative2()
{
try {
$this->cb->depositarDinheiro(-1);
}

catch (ExcecaoContaBancaria $e) {
$this->assertEquals(0, $this->cb->getSaldo());

return;
}

$this->fail();
}

/**
* @covers ContaBancaria::getSaldo
* @covers ContaBancaria::depositarDinheiro
* @covers ContaBancaria::sacarDinheiro
*/

public function testDepositarSacarDinheiro()
{
$this->assertEquals(0, $this->cb->getSaldo());
$this->cb->depositarDinheiro(1);
$this->assertEquals(1, $this->cb->getSaldo());
$this->cb->sacarDinheiro(1);
$this->assertEquals(0, $this->cb->getSaldo());
}
}
?>

Também é possível especificar que um teste não deve cobrir qualquer método usando a anotação @coversNothing (veja @coversNothing). Isso pode ser útil quando escrever testes de integração para certificar-se de que você só gerará cobertura de código com testes unitários.

Exemplo 14.3: Um teste que especifica que nenhum método deve ser coberto

<?php
class IntegracaoLivroDeVisitasTest extends PHPUnit_Extensions_Database_TestCase
{
/**
* @coversNothing
*/
public function testAdicionaEntrada()
{
$livrodevisitas = new LivroDeVisitas();
$livrodevisitas->adicionaEntrada("suzy", "Olá mundo!");

$tabelaQuery = $this->getConnection()->criarTabelaQuery(
'livrodevisitas', 'SELECT * FROM livrodevisitas'
);
$expectedTable = $this->criarConjuntoDadosXmlPlano("livroEsperado.xml")
->getTabela("livrodevisitas");
$this->assertTablesEqual($tabelaEsperada, $tabelaQuery);
}
}
?>


Ignorando Blocos de Código

Às vezes você tem blocos de código que não pode testar e que pode querer ignorar durante a análise de cobertura de código. O PHPUnit permite que você o faça usando as anotações @codeCoverageIgnore, @codeCoverageIgnoreStart e @codeCoverageIgnoreEnd como mostrado em Exemplo 14.4.

Exemplo 14.4: Usando as anotações @codeCoverageIgnore, @codeCoverageIgnoreStart e @codeCoverageIgnoreEnd

<?php
/**
* @codeCoverageIgnore
*/
class Foo
{
public function bar()
{
}
}

class Bar
{
/**
* @codeCoverageIgnore
*/
public function foo()
{
}
}

if (FALSE) {
// @codeCoverageIgnoreStart
print '*';
// @codeCoverageIgnoreEnd
}
?>

As linhas de código que estão marcadas para serem ignoradas usando as anotações são contadas como executadas (se forem executáveis) e não serão destacadas.

Incluindo e Excluindo Arquivos

Por padrão, todos os arquivos de código-fonte que contêm pelo menos uma linha de código que tenha sido executada (e apenas esses arquivos) são incluídos no relatório. Os arquivos de código-fonte que são incluídos no relatório podem ser filtrados usando uma abordagem de lista-negra ou lista-branca.

A lista-negra é pré-preenchida com todos os arquivos de código-fonte do próprio PHPUnit assim como os testes. Quando a lista-branca está vazia (padrão), a lista-negra é usada. Quando a lista-branca não está vazia, a lista-branca é usada. Cada arquivo na lista-branca é adicionado ao relatório de cobertura de código independentemente de ter sido executado ou não. Todas as linhas em tal arquivo, incluindo aquelas que não são executáveis, são contadas como não executadas.

Quando você define processUncoveredFilesFromWhitelist="true" na sua configuração do PHPUnit (veja “Incluindo e Excluindo Arquivos para Cobertura de Código”) então esses arquivos serão incluídos pelo PHP_CodeCoverage para calcular adequadamente o número de linhas executáveis.

Nota

Por favor, note que o carregamento de arquivos de código-fonte que é realizado quando processUncoveredFilesFromWhitelist="true" é definido pode causar problemas quando um arquivo de código-fonte contém código fora do escopo de uma classe ou função, por exemplo.

O arquivo de configuração XML do PHPUnit (veja “Incluindo e Excluindo Arquivos para Cobertura de Código”) pode ser usado para controlar as listas branca e negra. Usar uma lista-branca é a melhor prática recomendada para controlar a lista de arquivos incluídos no relatório de cobertura de código.

Casos Extremos

De modo geral é seguro dizer que o PHPUnit oferece a você uma informação de cobertura de código "baseada em linha", mas devido ao modo que a informação é coletada, existem alguns casos extremos dignos de atenção.

Exemplo 14.5:

<?php
// Por ser "baseado em linha" e não em declaração,
// uma linha sempre terá um estado de cobertura
if(false) esta_chamada_de_funcao_aparece_como_coberta();

// Devido ao modo que a cobertura de código funciona internamente,
// estas duas linhas são especiais.
// Esta linha vai aparecer como não-executável
if(false)
// Esta linha vai aparecer como coberta, pois de fato é a cobertura
// da declaração if da linha anterior que é mostrada aqui!
tambem_vai_aparecer_como_coberta();

// Para evitar isso é necessário usar chaves
if(false){
esta_chamada_nunca_aparecera_como_coberta();
}
?>

Prev Next
1. Automatizando Testes
2. Objetivos do PHPUnit
3. Instalando o PHPUnit
PEAR
Composer
PHP Archive (PHAR)
Pacotes opcionais
Atualizando
4. Escrevendo Testes para o PHPUnit
Dependências de Testes
Provedores de Dados
Testando Exceções
Testando Erros PHP
Testando Saídas
Asserções
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertContainsOnlyInstancesOf()
assertCount()
assertEmpty()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertInstanceOf()
assertInternalType()
assertJsonFileEqualsJsonFile()
assertJsonStringEqualsJsonFile()
assertJsonStringEqualsJsonString()
assertLessThan()
assertLessThanOrEqual()
assertNull()
assertObjectHasAttribute()
assertRegExp()
assertStringMatchesFormat()
assertStringMatchesFormatFile()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
Saída de Erro
Casos Extremos
5. O executor de testes em linha-de-comando
Comutadores de linha-de-comando
6. Ambientes
Mais setUp() que tearDown()
Variantes
Compartilhando Ambientes
Estado Global
7. Organizando Testes
Compondo uma Suíte de Testes usando o Sistema de Arquivos
Compondo uma Suíte de Testes Usando uma Configuração XML
8. Testando Bancos de Dados
Fornecedores Suportados para Testes de Banco de Dados
Dificuldades em Testes de Bancos de Dados
Os quatro estágios dos testes com banco de dados
1. Limpar o Banco de Dados
2. Configurar o ambiente
3–5. Executar Teste, Verificar saída e Teardown
Configuração de Caso de Teste de Banco de Dados do PHPUnit
Implementando getConnection()
Implementando getDataSet()
E quanto ao Esquema do Banco de Dados (DDL)?
Dica: Use seu próprio Caso Abstrato de Teste de Banco de Dados
Entendendo Conjunto de Dados e Tabelas de Dados
Implementações disponíveis
Cuidado com Chaves Estrangeiras
Implementando seus próprios Conjuntos de Dados/ Tabelas de Dados
A API de Conexão
API de Asserções de Banco de Dados
Assertando a contagem de linhas de uma Tabela
Assertando o Estado de uma Tabela
Assertando o Resultado de uma Query
Assertando o Estado de Múltiplas Tabelas
Perguntas Mais Frequentes
O PHPUnit vai (re)criar o esquema do banco de dados para cada teste?
Sou forçado a usar PDO em minha aplicação para que a Extensão para Banco de Dados funcione?
O que posso fazer quando recebo um Erro Too much Connections?
Como lidar com NULL usando Conjuntos de Dados XML Plano / CSV?
9. Testes Incompletos e Pulados
Testes Incompletos
Pulando Testes
Pulando Testes usando @requires
10. Dublês de Testes
Esboços (stubs)
Objetos Falsos
Esboçando e Falsificando Serviços Web
Esboçando o Sistema de Arquivos
11. Práticas de Teste
Durante o Desenvolvimento
Durante a Depuração
12. Desenvolvimento Guiado por Testes
Exemplo da Conta Bancária
13. Desenvolvimento Guiado por Comportamento
Exemplo do Jogo de Boliche
14. Análise de Cobertura de Código
Especificando métodos cobertos
Ignorando Blocos de Código
Incluindo e Excluindo Arquivos
Casos Extremos
15. Outros Usos para Testes
Documentação Ágil
Testes Inter-Equipes
16. Gerador de Esqueleto
Gerando um Esqueleto de Classe de Caso de Teste
Gerando uma Classe Esqueleto de uma Classe de Caso de Teste
17. PHPUnit e Selenium
Servidor Selenium
Instalação
PHPUnit_Extensions_Selenium2TestCase
PHPUnit_Extensions_SeleniumTestCase
18. Registrando
Resultados de Teste (XML)
Resultados de Teste (TAP)
Resultados de Teste (JSON)
Cobertura de Código (XML)
Cobertura de Código (TEXTO)
19. Estendendo o PHPUnit
Subclasse PHPUnit_Framework_TestCase
Escreva asserções personalizadas
Implementando PHPUnit_Framework_TestListener
Subclasse PHPUnit_Extensions_TestDecorator
Implementando PHPUnit_Framework_Test
A. Assertions
B. Anotações
@author
@backupGlobals
@backupStaticAttributes
@codeCoverageIgnore*
@covers
@coversNothing
@dataProvider
@depends
@expectedException
@expectedExceptionCode
@expectedExceptionMessage
@group
@outputBuffering
@requires
@runTestsInSeparateProcesses
@runInSeparateProcess
@test
@testdox
@ticket
C. O arquivo de configuração XML
PHPUnit
Suítes de Teste
Grupos
Incluindo e Excluindo Arquivos para Cobertura de Código
Registrando
Ouvintes de Teste
Setting PHP INI settings, Constants and Global Variables
Configurando Navegadores para Selenium RC
D. Índice
E. Bibliografia
F. Copyright