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

 

Na ciência da computação, cobertura de código é um mensuração usada para descrever o grau em que um código fonte de um programa é testado por uma suíte de testes particular. Um programa com cobertura de código alta foi mais exaustivamente testado e tem uma menor chance de conter erros de software do que um programa com baixa cobertura de código.

 
 --Wikipedia

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. Ele faz uso do componente PHP_CodeCoverage, que por sua vez utiliza a funcionalidade de cobertura de código fornecido pela extensão Xdebug para PHP.

Nota

Xdebug não é distribuído como parte do PHPUnit. Se você Se você receber um aviso durante a execução de testes que a extensão Xdebug não está carregada, isso significa que Xdebug ou não está instalada ou não está configurada corretamente. Antes que você possa usar as funcionalidades de análise de cobertura de código no PHPUnit, você deve ler o manual de instalação Xdebug.

PHPUnit pode gerar um relatório de cobertura de código baseado em HTML, tal como arquivos de registros baseado em XML com informações de cobertura de código em vários formatos (Clover, Crap4J, PHPUnit). Informação de cobertura de código também pode ser relatado como texto (e impresso na STDOUT) e exportado como código PHP para futuro processamento.

Por favor, consulte o Capítulo 3 para uma lista de opções de linha de comando que controlam a funcionalidade de cobertura de código, bem como em “Registrando” para as definições de configurações relevantes.

Métricas de Software para Cobertura de Código

Várias métricas de software existem para mensurar a cobertura de código:

Cobertura de Linha

A métrica de software Cobertura de Linha mensura se cada linha executável foi executada.

Cobertura de Função e Método

A métrica de software Cobertura de Função e Método mensura se cada função ou método foi invocado. PHP_CodeCoverage só considera uma função ou método como coberto quando todas suas linhas executáveis são cobertas.

Cobertura de Classe e Trait

A métrica de software Cobertura de Classe e Trait mensura se cada método de uma classe ou trait é coberto. PHP_CodeCoverage só considera uma classe ou trait como coberta quando todos seus métodos são cobertos.

Cobertura de Código de Operação

A métrica de software Cobertura de Código de Operação mensura se cada código de operação de uma função ou método foi executado enquanto executa a suíte de teste. Uma linha de código geralmente compila em mais de um código de operação. Cobertura de Linha considera uma linha de código como coberta logo que um dos seus códigos de operações é executado.

Cobertura de Ramo

A métrica de software Cobertura de Ramo mensura se cada expressão booleana de cada estrutura de controle avaliada a true e false enquanto executa a suíte de teste.

Cobertura de Caminho

A métrica de software Cobertura de Caminho mensura se cada um dos caminhos de execução possíveis em uma função ou método foi seguido durante a execução da suíte de teste. Um caminho de execução é uma sequência única de ramos a partir da entrada de uma função ou método para a sua saída.

Índice de Anti-Patterns de Mudança de Risco (CRAP - Change Risk Anti-Patterns)

O Índice de Anti-Patterns de Mudança de Risco (CRAP - Change Risk Anti-Patterns) é calculado baseado na complexidade ciclomática e cobertura de código de uma unidade de código. Código que não é muito complexo e tem uma cobertura de teste adequada terá um baixo índice CRAP. O índice CRAP pode ser reduzido escrevendo testes e refatorando o código para reduzir sua complexidade.

Nota

As métricas de software Cobertura de Código de Operação, Cobertura de Ramo e Cobertura de Caminho ainda não são suportadas pelo PHP_CodeCoverage.

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 de cobertura de código.

Por padrão, uma lista-negra é usada para excluir arquivos do relatório de cobertura de código. Essa lista-negra é previamente preenchida com os arquivos-fonte do PHPUnit e suas dependências.

É uma boa prática usar um lista-branca ao invés da lista-negra mencionada acima.

Opcionalmente, todos arquivos da lista-branca pode ser adicionados ao relatório de cobertura de código pela definição addUncoveredFilesFromWhitelist="true" em suas configurações do PHPUnit (veja “Incluindo e Excluindo Arquivos para Cobertura de Código”). Isso permite a inclusão de arquivos que ainda não são testados em tudo. Se você quer obter informação sobre quais linhas de um tal arquivo descoberto são executadas, por exemplo, você também precisa definir processUncoveredFilesFromWhitelist="true" em suas configurações do PHPUnit (veja “Incluindo e Excluindo Arquivos para Cobertura de Código”).

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.

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

Exemplo 11.1: 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
}

exit; // @codeCoverageIgnore
?>


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

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 11.2 mostra um exemplo.

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

<?php
class BankAccountTest extends PHPUnit_Framework_TestCase
{
    protected $ba;

    protected function setUp()
    {
        $this->ba = new BankAccount;
    }

    /**
     * @covers BankAccount::getBalance
     */
    public function testBalanceIsInitiallyZero()
    {
        $this->assertEquals(0, $this->ba->getBalance());
    }

    /**
     * @covers BankAccount::withdrawMoney
     */
    public function testBalanceCannotBecomeNegative()
    {
        try {
            $this->ba->withdrawMoney(1);
        }

        catch (BankAccountException $e) {
            $this->assertEquals(0, $this->ba->getBalance());

            return;
        }

        $this->fail();
    }

    /**
     * @covers BankAccount::depositMoney
     */
    public function testBalanceCannotBecomeNegative2()
    {
        try {
            $this->ba->depositMoney(-1);
        }

        catch (BankAccountException $e) {
            $this->assertEquals(0, $this->ba->getBalance());

            return;
        }

        $this->fail();
    }

    /**
     * @covers BankAccount::getBalance
     * @covers BankAccount::depositMoney
     * @covers BankAccount::withdrawMoney
     */
    public function testDepositWithdrawMoney()
    {
        $this->assertEquals(0, $this->ba->getBalance());
        $this->ba->depositMoney(1);
        $this->assertEquals(1, $this->ba->getBalance());
        $this->ba->withdrawMoney(1);
        $this->assertEquals(0, $this->ba->getBalance());
    }
}
?>


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 11.3: Um teste que especifica que nenhum método deve ser coberto

<?php
class GuestbookIntegrationTest extends PHPUnit_Extensions_Database_TestCase
{
    /**
     * @coversNothing
     */
    public function testAddEntry()
    {
        $guestbook = new Guestbook();
        $guestbook->addEntry("suzy", "Hello world!");

        $queryTable = $this->getConnection()->createQueryTable(
            'guestbook', 'SELECT * FROM guestbook'
        );

        $expectedTable = $this->createFlatXmlDataSet("expectedBook.xml")
                              ->getTable("guestbook");

        $this->assertTablesEqual($expectedTable, $queryTable);
    }
}
?>
      


Casos Extremos

Esta seção mostra casos extremos notáveis que induz a confundir a informação de cobertura de código.

Exemplo 11.4:

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

// 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!
    will_also_show_up_as_covered();

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


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