在本章中,你將學(xué)到 PHPUnit 中與代碼覆蓋率相關(guān)的一切功能。通過這部分功能,能夠了解在測試運(yùn)行過程中執(zhí)行了生產(chǎn)代碼的哪些部分。它使用了 php-code-coverage 組件,而這個(gè)組件又使用了 PHP 的 Xdebug 或 PCOV 擴(kuò)展或 PHPDBG 所提供的代碼覆蓋率功能。
PHPUnit 可以生成基于 HTML 的代碼覆蓋率報(bào)告,同時(shí)也能生成好幾種(Clover、Crap4J、PHPUnit)基于 XML 的代碼覆蓋率信息記錄文件。代碼覆蓋率信息也能以文本格式提供(同時(shí)可以輸出到 STDOUT)或以 PHP 代碼格式輸出以供進(jìn)一步處理。
目前存在多種軟件衡量標(biāo)準(zhǔn)用于衡量代碼覆蓋率:
Line Coverage
?):按單個(gè)可執(zhí)行行是否已執(zhí)行進(jìn)行計(jì)量。Branch Coverage
?):按控制結(jié)構(gòu)的分支進(jìn)行計(jì)量。測試套件運(yùn)行時(shí)每個(gè)控制結(jié)構(gòu)的布爾表達(dá)式求值為 ?true
?和 ?false
?各自計(jì)為一個(gè)分支。Path Coverage
?):按測試套件運(yùn)行時(shí)函數(shù)或者方法內(nèi)部所經(jīng)歷的執(zhí)行路徑進(jìn)行計(jì)量。一個(gè)執(zhí)行路徑指的是從進(jìn)入函數(shù)或方法一直到離開的過程中經(jīng)過各個(gè)分支的特定序列。Function and Method Coverage
?):按單個(gè)函數(shù)或方法是否已調(diào)用進(jìn)行計(jì)量。僅當(dāng)函數(shù)或方法的所有可執(zhí)行行全部已覆蓋時(shí) php-code-coverage 才將其視為已覆蓋。Class and Trait Coverage
?):按單個(gè)類或特質(zhì)的所有方法是否全部已覆蓋進(jìn)行計(jì)量。僅當(dāng)一個(gè)類或特質(zhì)的所有方法全部已覆蓋時(shí) php-code-coverage 才將其視為已覆蓋。Change Risk Anti-Patterns (CRAP) Index
?):是基于代碼單元的圈復(fù)雜度(cyclomatic complexity)與代碼覆蓋率計(jì)算得出的。不太復(fù)雜并具有恰當(dāng)測試覆蓋率的代碼將得出較低的 CRAP 指數(shù)??梢酝ㄟ^編寫測試或重構(gòu)代碼來降低其復(fù)雜性的方式來降低 CRAP 指數(shù)。為了告訴 PHPUnit 哪些源代碼文件要包含在代碼覆蓋率報(bào)告中,必須配置過濾器??梢杂妹钚羞x項(xiàng) ?--coverage-filter
? 或通過配置文件來完成。
?includeUncoveredFilesInCodeCoverageReport
?和 ?processUncoveredFilesForCodeCoverageReport
?配置設(shè)置可用于配置過濾器的使用方式:
includeUncoveredFilesInCodeCoverageReport="false"
? 意味著只有至少有一行已執(zhí)行代碼的文件才會包括在代碼覆蓋率報(bào)告中includeUncoveredFilesInCodeCoverageReport="true"
?(默認(rèn)值)意味著所有文件都會包括在代碼覆蓋率報(bào)告中,即使文件中沒有任何一行代碼被執(zhí)行過也一樣processUncoveredFilesForCodeCoverageReport="false"
?(默認(rèn)值)意味著沒有已執(zhí)行代碼行的文件會被加入到代碼覆蓋率報(bào)告中(如果設(shè)置了 ?includeUncoveredFilesInCodeCoverageReport="true"
?),但它并不會被 PHPUnit 加載,因此也不會對其進(jìn)行分析來獲取正確的可執(zhí)行代碼行信息processUncoveredFilesForCodeCoverageReport="true"
? 意味著沒有已執(zhí)行代碼行的文件會被 PHPUnit 加載,從而也能對其進(jìn)行分析來獲取正確的可執(zhí)行代碼行信息請注意,當(dāng)設(shè)置了 ?processUncoveredFilesForCodeCoverageReport="true"
? 時(shí)將對源代碼文件進(jìn)行載入,這在某些情況下可能導(dǎo)致問題,比如,源代碼文件包含有處于類或者函數(shù)作用域之外的代碼。
有時(shí),一些代碼塊是無法對其進(jìn)行測試的,因此希望在代碼覆蓋率分析中忽略它們。在 PHPUnit 中可以用 ?@codeCoverageIgnore
?、?@codeCoverageIgnoreStart
? 與 ?@codeCoverageIgnoreEnd
? 標(biāo)注來做到這點(diǎn),如示例 9.1 中所示。
示例 9.1 使用 ?@codeCoverageIgnore
?、?@codeCoverageIgnoreStart
? 和 ?@codeCoverageIgnoreEnd
? 標(biāo)注
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* @codeCoverageIgnore
*/
final class Foo
{
public function bar(): void
{
}
}
final class Bar
{
/**
* @codeCoverageIgnore
*/
public function foo(): void
{
}
}
if (false) {
// @codeCoverageIgnoreStart
print '*';
// @codeCoverageIgnoreEnd
}
exit; // @codeCoverageIgnore
代碼中被忽略掉的行(用標(biāo)注標(biāo)記為忽略)將會計(jì)為已執(zhí)行(如果它們是可執(zhí)行的),并且不會在代碼覆蓋情況中被高亮標(biāo)記。
?@covers
? 標(biāo)注可以用在測試代碼中來指明測試類(或測試方法)想要對哪些代碼部分進(jìn)行測試。如果提供了這個(gè)信息,則可以有效過濾代碼覆蓋率報(bào)告,僅包含所指定的代碼部分中的已執(zhí)行代碼。示例 9.2 展示了一個(gè)例子。
如果用 ?@covers
? 標(biāo)注指定了一個(gè)方法嗎,那么只有所指方法會被視為已覆蓋,這個(gè)方法所調(diào)用的方法不會視為已覆蓋。因此,如果用提取方法重構(gòu)了已覆蓋的方法,則需要添加相應(yīng)的 ?@covers
? 標(biāo)注。這就是推薦將此標(biāo)注用在類作用域而非方法作用域的原因。
示例 9.2 指明了要覆蓋的類的測試類
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
/**
* @covers \Invoice
* @uses \Money
*/
final class InvoiceTest extends TestCase
{
private $invoice;
protected function setUp(): void
{
$this->invoice = new Invoice;
}
public function testAmountInitiallyIsEmpty(): void
{
$this->assertEquals(new Money, $this->invoice->getAmount());
}
}
示例 9.3 指明了要覆蓋哪個(gè)方法的測試
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class BankAccountTest extends TestCase
{
private $ba;
protected function setUp(): void
{
$this->ba = new BankAccount;
}
/**
* @covers \BankAccount::getBalance
*/
public function testBalanceIsInitiallyZero(): void
{
$this->assertSame(0, $this->ba->getBalance());
}
/**
* @covers \BankAccount::withdrawMoney
*/
public function testBalanceCannotBecomeNegative(): void
{
try {
$this->ba->withdrawMoney(1);
}
catch (BankAccountException $e) {
$this->assertSame(0, $this->ba->getBalance());
return;
}
$this->fail();
}
/**
* @covers \BankAccount::depositMoney
*/
public function testBalanceCannotBecomeNegative2(): void
{
try {
$this->ba->depositMoney(-1);
}
catch (BankAccountException $e) {
$this->assertSame(0, $this->ba->getBalance());
return;
}
$this->fail();
}
/**
* @covers \BankAccount::getBalance
* @covers \BankAccount::depositMoney
* @covers \BankAccount::withdrawMoney
*/
public function testDepositWithdrawMoney(): void
{
$this->assertSame(0, $this->ba->getBalance());
$this->ba->depositMoney(1);
$this->assertSame(1, $this->ba->getBalance());
$this->ba->withdrawMoney(1);
$this->assertSame(0, $this->ba->getBalance());
}
}
同時(shí),可以用 ?@coversNothing
? 標(biāo)注來指明一個(gè)測試不覆蓋任何方法。這可以在編寫集成測試時(shí)用于確保代碼覆蓋全部來自單元測試。
示例 9.4 指明應(yīng)當(dāng)不覆蓋任何方法的測試
<?php declare(strict_types=1);
use PHPUnit\DbUnit\TestCase
final class GuestbookIntegrationTest extends TestCase
{
/**
* @coversNothing
*/
public function testAddEntry(): void
{
$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);
}
}
本節(jié)中展示了一些值得注意的邊緣情況,在這些邊緣情況中可能出現(xiàn)令人迷惑的代碼覆蓋率信息。
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
// 因?yàn)槭恰盎谛小钡亩腔谡Z句的覆蓋率
// 一行始終只能有一種覆蓋狀態(tài)
if (false) this_function_call_shows_up_as_covered();
// 由于代碼覆蓋率的內(nèi)部工作方式,這兩行顯得很特殊。
// 這一行會顯示為非可執(zhí)行
if (false)
// 這一行會顯示為已覆蓋,
// 實(shí)際上是上一行的 if 語句的覆蓋信息顯示在這了!
will_also_show_up_as_covered();
// 要避免這種情況,必須使用大括號
if (false) {
this_call_will_never_show_up_as_covered();
}
更多建議: