Na ciência da computação, cobertura de código é uma medida usada para descrever o grau em que um código fonte de um programa é testado por uma particular suíte de testes. 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 fornecida pela extensão Xdebug para PHP.
Xdebug não é distribuído como parte do PHPUnit. Se você receber um aviso durante a execução de testes que a extensão Xdebug não está carregada, isso significa que Xdebug 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 relatada como texto (e impressa na STDOUT) e exportada 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.
Várias métricas de software existem para mensurar a cobertura de código:
A métrica de software Cobertura de Linha mensura se cada linha executável foi executada.
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.
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.
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.
A métrica de software Cobertura de Ramo mensura
se cada expressão booleana de cada estrutura de controle avaliou
tanto para true
quanto para false
enquanto
executa a suíte de teste.
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.
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.
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.
É obrigatório configurar uma whitelist para dizer ao
PHPUnit quais arquivos de código-fonte incluir no relatório de cobertura de código.
Isso pode ser feito usando a opção de linha de comando --whitelist
ou via arquivo de configuração (veja “Lista-branca de Arquivos para Cobertura de Código”).
Opcionalmente, todos arquivos da lista-branca podem ser adicionados ao relatório
de cobertura de código ao definiri addUncoveredFilesFromWhitelist="true"
em sua configuração do PHPUnit (veja “Lista-branca de Arquivos para Cobertura de Código”). Isso permite a
inclusão de arquivos que não são testados ainda no todo. Se você quer ter
informação sobre quais linhas de um tal arquivo não-coberto são executáveis,
por exemplo, você também precisa definir
processUncoveredFilesFromWhitelist="true"
em sua
configuração do PHPUnit (veja “Lista-branca de Arquivos para Cobertura de Código”).
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.
À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 deixa você fazer isso
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 use PHPUnit\Framework\TestCase; /** * @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.
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 eles querem cobrir
<?php use PHPUnit\Framework\TestCase; class BankAccountTest extends 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 use PHPUnit\Framework\TestCase; 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); } } ?>
Esta seção mostra casos extremos notáveis que induz a confundir a informação de cobertura de código.
Exemplo 11.4:
<?php use PHPUnit\Framework\TestCase; // 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 como 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(); } ?>