Prev Next

第14章 コードカバレッジ解析

 

The beauty of testing is found not in the effort but in the effiency.

- テストの美学は、どれだけ汗を流したかではなく どれだけ効率的であるかである。

Knowing what should be tested is beautiful, and knowing what is being tested is beautiful.

- 何をテストすべきなのかを知ること、何がテストされているのかを知ること。 これが大切だ。

 
  --Murali Nandigama

この章では、PHPUnit のコードカバレッジ機能について学びます。 これは、テストを実行したときに、実装コードのどの部分が実行されたかを調べるものです。 次のような疑問に対する答えとなるでしょう。

  • テストされていないコードを見つけるには? 言い換えれば、まだテストで カバーされていない部分を見つけるには?

  • 完全にテストができたことをどうやって確認するの?

ステートメントカバレッジというのは、たとえば 100 行のコードで構成されるメソッドがあった場合に、 もしテストで実際に実行されたのがそのうちの 75 行だけだったなら、 そのメソッドのコードカバレッジは 75 パーセントだと考えるということです。

PHPUnit のコードカバレッジ解析では PHP_CodeCoverage コンポーネントを使っています。このコンポーネントは、 Xdebug 拡張モジュールが提供するステートメントカバレッジ機能を利用しています。

それでは、例 12.3BankAccount クラスについての コードカバレッジレポートを作成してみましょう。

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

...

Time: 0 seconds

OK (3 tests, 3 assertions)

Generating report, this may take a moment.

図 14.1 は、コードカバレッジレポートの一部を抜粋したものです。 テスト時に実行された行は、緑色で強調表示されます。 実行可能なコードであるにもかかわらず実行されなかった行については赤色で強調表示されます。 また、"無意味なコード" についてはグレーで強調表示されます。 行の左にある数字は、その行をカバーするテストの数を表します。

図14.1 setBalance() のコードカバレッジ

setBalance() のコードカバレッジ

BankAccount のコードカバレッジレポートからわかることは、 setBalance()depositMoney() をコールするテストがまだ存在しないということ、 そして withdrawMoney() に正しい値を指定した場合のテストも存在しないということです。 BankAccountTest クラスに追加するテストを 例 14.1 に示します。これによって、BankAccount クラスのテストケースを完全に網羅できるようになります。

例 14.1: 完全なコードカバレッジを達成するために欠けているテスト

<?php
require_once 'BankAccount.php';

class BankAccountTest extends PHPUnit_Framework_TestCase
{
// ...

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());
}
}
?>

図 14.2 は、 テストを追加した後の setBalance() のコードカバレッジです。

図14.2 setBalance() にテストを追加した後のコードカバレッジ

setBalance() にテストを追加した後のコードカバレッジ

カバーするメソッドの指定

テストコードで @covers アノテーション (表 B.1) を参照ください) を使用すると、 そのテストメソッドがどのメソッドをテストしたいのかを指定することができます。 これを指定すると、指定したメソッドのコードカバレッジ情報のみを考慮します。 例 14.2 に例を示します。

例 14.2: どのメソッドを対象とするかを指定したテスト

<?php
require_once 'BankAccount.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());
}
}
?>

あるテストが、一切メソッドをカバーしてはならないことも指定できます。 そのために使うのが @coversNothing アノテーションです。 (@coversNothing を参照ください)。 これは、インテグレーションテストを書く際に ユニットテストだけのコードカバレッジを生成させたい場合に便利です。

例 14.3: どのメソッドもカバーすべきでないことを指定したテスト

<?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);
}
}
?>


コードブロックの無視

どうしてもテストができないコードブロックなどを、 コードカバレッジ解析時に無視させたいこともあるでしょう。 PHPUnit でこれを実現するには、 @codeCoverageIgnore@codeCoverageIgnoreStart および @codeCoverageIgnoreEnd アノテーションを 例 14.4 のように使用します。

例 14.4: @codeCoverageIgnore@codeCoverageIgnoreStart および @codeCoverageIgnoreEnd アノテーションの使用法

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

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

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

これらのアノテーションを使って無視するよう指定された行は、 (たとえ実行されなかったとしても) 実行されたものとみなされ、 強調表示されません。

ファイルのインクルードや除外

デフォルトでは、1 行でもコードが実行されたソースコードファイルはすべて (そしてそのようなファイルのみが) レポートに含められます。 レポートに含まれるソースコードファイルは、 ホワイトリスト方式あるいはブラックリスト方式でフィルタリングすることができます。

ブラックリストには、PHPUnit 自身のソースコードファイルやテストファイルがデフォルトで登録されています。 ホワイトリストが空 (デフォルト) の場合はブラックリストを使用し、 ホワイトリストが空でない場合はホワイトリストを使用します。 ホワイトリスト内の各ファイルは、そのファイルが実行されるかどうかにかかわらず レポートに追加されます。追加されたファイルのすべての行は、 実行不能な行も含めて「実行されなかった行」とみなします。

PHPUnit の設定で processUncoveredFilesFromWhitelist="true" (「コードカバレッジ対象のファイルの追加や除外」 を参照ください) とすると、これらのファイルが PHP_CodeCoverage に渡され、実行可能な行数を適切に算出します。

注記

processUncoveredFilesFromWhitelist="true" が設定されている場合のソースコードファイルの読み込みでは、 もしクラスや関数のスコープから外れるコードが含まれていたときに問題が起こる可能性があります。

PHPUnit の XML 設定ファイル (「コードカバレッジ対象のファイルの追加や除外」 を参照ください) を使って、ブラックリストやホワイトリストを制御することができます。 おすすめの方法は、ホワイトリスト方式を使ってコードカバレッジレポートに含めるファイルを制御することです。

エッジケース

ほとんどの場面では、PHPUnit が「行単位の」コードカバレッジ情報を提供してくれると考えて間違いないでしょう。 しかし、その情報収集の方法が原因で、特筆すべきエッジケースもいくつか存在します。

例 14.5:

<?php
// カバレッジは「行単位」であって文単位ではないので、
// 一行にまとめられた行はひとつのカバレッジ状態しか持ちません
if(false) this_function_call_shows_up_as_covered();

// コードカバレッジの内部動作上、これら 2 行は特別です。
// 次の行は「実行されていない」となります
if(false)
// 次の行は「実行されている」となります
// 実際のところ、ひとつ上の if 文のカバレッジ情報がここに表示されることになるからです!
will_also_show_up_as_coveraged();

// これを避けるには、必ず波括弧を使わなければなりません
if(false) {
this_call_will_never_show_up_as_covered();
}
?>

Prev Next
1. 自動テスト
2. PHPUnit の目標
3. PHPUnit のインストール
PEAR
Composer
PHP Archive (PHAR)
オプションのパッケージ
アップグレード
4. PHPUnit 用のテストの書き方
テストの依存性
データプロバイダ
例外のテスト
PHP のエラーのテスト
出力内容のテスト
アサーション
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()
5. コマンドラインのテストランナー
Command-Line switches
6. フィクスチャ
tearDown() よりも setUp()
バリエーション
フィクスチャの共有
グローバルな状態
7. テストの構成
ファイルシステムを用いたテストスイートの構成
XML 設定ファイルを用いたテストスイートの構成
8. データベースのテスト
データベースのテストに対応しているベンダー
データベースのテストの難しさ
データベーステストの四段階
1. データベースのクリーンアップ
2. フィクスチャの準備
3–5. テストの実行、結果の検証、そして後始末
PHPUnit のデータベーステストケースの設定
getConnection() の実装
getDataSet() の実装
データベーススキーマ (DDL) とは?
ヒント: 自前でのデータベーステストケースの抽象化
データセットとデータテーブルについて知る
利用できる実装
外部キーには注意
自作のデータセットやデータテーブルの実装
接続 API
データベースアサーション API
テーブルの行数のアサーション
テーブルの状態のアサーション
クエリの結果のアサーション
複数のテーブルの状態のアサーション
よくある質問
PHPUnit は、テストごとにデータベーススキーマを作り直すの?
PDO を使ったアプリケーションじゃないと Database Extension を使えないの?
Too much Connections というエラーが出たらどうすればいい?
フラット XML や CSV のデータセットで NULL を扱う方法は?
9. 不完全なテスト・テストの省略
不完全なテスト
テストの省略
@requires によるテストのスキップ
10. テストダブル
スタブ
モックオブジェクト
ウェブサービスのスタブおよびモック
ファイルシステムのモック
11. テストの進め方
開発中のテスト
デバッグ中のテスト
12. テスト駆動開発
銀行口座の例
13. 振舞駆動開発
ボウリングゲームの例
14. コードカバレッジ解析
カバーするメソッドの指定
コードブロックの無視
ファイルのインクルードや除外
エッジケース
15. テストのその他の使用法
アジャイルな文書作成
複数チームでのテスト
16. 雛形ジェネレータ
テストケースクラスの雛形の作成
テストケースクラスからのクラスの雛形の作成
17. PHPUnit と Selenium
Selenium Server
インストール
PHPUnit_Extensions_Selenium2TestCase
PHPUnit_Extensions_SeleniumTestCase
18. ログ出力
テスト結果 (XML)
テスト結果 (TAP)
テスト結果 (JSON)
コードカバレッジ (XML)
コードカバレッジ (テキスト)
19. PHPUnit の拡張
PHPUnit_Framework_TestCase のサブクラスの作成
カスタムアサーションの作成
PHPUnit_Framework_TestListener の実装
PHPUnit_Extensions_TestDecorator のサブクラスの作成
PHPUnit_Framework_Test の実装
A. アサーション
B. アノテーション
@author
@backupGlobals
@backupStaticAttributes
@codeCoverageIgnore*
@covers
@coversNothing
@dataProvider
@depends
@expectedException
@expectedExceptionCode
@expectedExceptionMessage
@group
@outputBuffering
@preserveGlobalState
@requires
@runTestsInSeparateProcesses
@runInSeparateProcess
@test
@testdox
@ticket
C. XML 設定ファイル
PHPUnit
テストスイート
グループ
コードカバレッジ対象のファイルの追加や除外
ログ出力
テストリスナー
PHP INI 項目や定数、グローバル変数の設定
Selenium RC の設定ブラウザ
D. 目次
E. 参考文献
F. 著作権