ここ数年仕事ではPHPを使って開発をしていますが、最近品質について考える機会が増えたこともあり、これを機にPHPUnitと周辺のモジュールの仕組みを理解してより楽にテストができるようにしたいと思います。
PHPUnitは?
Created PHPUnit. Co-Founded thePHP.cc. Helps PHP developers build better software.
PHPUnitの作者は、Sebastian Bergmannという方でthePHP.ccのファウンダーのようです。関連情報は以下にて確認してみてください。
PHPUnitの構成
PHPUnitは以下のような構成になっています。
- phpunit
- phpcode-coverage
- phpfile-iterator
- phptext-template
- phptimer
- phptoken-stream
- phpunit
- phpunitmock-objects
これらはGitHub上ではそれぞれ別々のリポジトリに分かれていますが、phpunitが本体でそれ以外はデフォルトの関連ライブラリという位置づけになるかと思います。
PHPUnitのsrc構成
モックオブジェクトなどを除いたphpunitだけのパッケージとクラス構成を見てみるとこんな感じになります。
├── Exception.php
├── Extensions
│ ├── GroupTestSuite.php
│ ├── PhptTestCase.php
│ ├── PhptTestSuite.php
│ ├── RepeatedTest.php
│ ├── TestDecorator.php
│ └── TicketListener.php
├── ForwardCompatibility
│ └── TestCase.php
├── Framework
│ ├── Assert
│ │ └── Functions.php
│ ├── Assert.php
│ ├── AssertionFailedError.php
│ ├── BaseTestListener.php
│ ├── CodeCoverageException.php
│ ├── Constraint
│ │ ├── And.php
│ │ ├── ArrayHasKey.php
│ │ ├── ArraySubset.php
│ │ ├── Attribute.php
│ │ ├── Callback.php
│ │ ├── ClassHasAttribute.php
│ │ ├── ClassHasStaticAttribute.php
│ │ ├── Composite.php
│ │ ├── Count.php
│ │ ├── Exception.php
│ │ ├── ExceptionCode.php
│ │ ├── ExceptionMessage.php
│ │ ├── ExceptionMessageRegExp.php
│ │ ├── FileExists.php
│ │ ├── GreaterThan.php
│ │ ├── IsAnything.php
│ │ ├── IsEmpty.php
│ │ ├── IsEqual.php
│ │ ├── IsFalse.php
│ │ ├── IsFinite.php
│ │ ├── IsIdentical.php
│ │ ├── IsInfinite.php
│ │ ├── IsInstanceOf.php
│ │ ├── IsJson.php
│ │ ├── IsNan.php
│ │ ├── IsNull.php
│ │ ├── IsTrue.php
│ │ ├── IsType.php
│ │ ├── JsonMatches
│ │ │ └── ErrorMessageProvider.php
│ │ ├── JsonMatches.php
│ │ ├── LessThan.php
│ │ ├── Not.php
│ │ ├── ObjectHasAttribute.php
│ │ ├── Or.php
│ │ ├── PCREMatch.php
│ │ ├── SameSize.php
│ │ ├── StringContains.php
│ │ ├── StringEndsWith.php
│ │ ├── StringMatches.php
│ │ ├── StringStartsWith.php
│ │ ├── TraversableContains.php
│ │ ├── TraversableContainsOnly.php
│ │ └── Xor.php
│ ├── Constraint.php
│ ├── Error
│ │ ├── Deprecated.php
│ │ ├── Notice.php
│ │ └── Warning.php
│ ├── Error.php
│ ├── Exception.php
│ ├── ExceptionWrapper.php
│ ├── ExpectationFailedException.php
│ ├── IncompleteTest.php
│ ├── IncompleteTestCase.php
│ ├── IncompleteTestError.php
│ ├── InvalidCoversTargetException.php
│ ├── OutputError.php
│ ├──PHPUnit_Framework_CoveredCodeNotExecutedException.php
│ ├──PHPUnit_Framework_MissingCoversAnnotationException.php
│ ├── RiskyTest.php
│ ├── RiskyTestError.php
│ ├── SelfDescribing.php
│ ├── SkippedTest.php
│ ├── SkippedTestCase.php
│ ├── SkippedTestError.php
│ ├── SkippedTestSuiteError.php
│ ├── SyntheticError.php
│ ├── Test.php
│ ├── TestCase.php
│ ├── TestFailure.php
│ ├── TestListener.php
│ ├── TestResult.php
│ ├── TestSuite
│ │ └── DataProvider.php
│ ├── TestSuite.php
│ ├── UnintentionallyCoveredCodeError.php
│ ├── Warning.php
│ └── WarningTestCase.php
├── Runner
│ ├── BaseTestRunner.php
│ ├── Exception.php
│ ├── Filter
│ │ ├── Factory.php
│ │ ├── Group
│ │ │ ├── Exclude.php
│ │ │ └── Include.php
│ │ ├── Group.php
│ │ └── Test.php
│ ├── StandardTestSuiteLoader.php
│ ├── TestSuiteLoader.php
│ └── Version.php
├── TextUI
│ ├── Command.php
│ ├── ResultPrinter.php
│ └── TestRunner.php
└── Util
├── Blacklist.php
├── Configuration.php
├── ConfigurationGenerator.php
├── ErrorHandler.php
├── Fileloader.php
├── Filesystem.php
├── Filter.php
├── Getopt.php
├── GlobalState.php
├── InvalidArgumentHelper.php
├── Log
│ ├──JSON.php
│ ├──JUnit.php
│ ├── TAP.php
│ └── TeamCity.php
├──PHP
│ ├── Default.php
│ ├── Template
│ │ └── TestCaseMethod.tpl.dist
│ ├──Windows.php
│ └── eval-stdin.php
├──PHP.php
├── Printer.php
├──Regex.php
├── String.php
├── Test.php
├── TestDox
│ ├── NamePrettifier.php
│ ├── ResultPrinter
│ │ ├── HTML.php
│ │ ├── Text.php
│ │ └──XML.php
│ └── ResultPrinter.php
├── TestSuiteIterator.php
├── Type.php
└──XML.php
PHPUnitのメイン処理
PHPUnitでテストを実行するときのメイン処理をざくっと見てみます。
PHPUnit_TextUI_CommandPHPUnit_TextUI_TestRunnerTextUIのCommandとTestRunnerは、phpunitのエントリポイントでphpunit実行時CommandからTestRunnerを呼び出し処理を開始します。
PHPUnit_Framework_Test(interface)PHPUnit_Framework_TestSuiteTestRunnerはTestSuiteを作成しTestSuiteのメイン処理であるrun処理を実行します。
PHPUnit_Framework_Test(interface)PHPUnit_Framework_AssertPHPUnit_Framework_TestCasePHPUnit_Framework_TestResult
TestSuiteはforeachにて順次TestCaseのテストを実行します。また、TestCaseの親クラスにはAssertがいてアサーションができるようになっています。
テストを実行する該当ソースをチェックする
TestSuite.php
TestRunnerから呼び出されたTestSuiteは、run処理にて対象テストをforeachして順次テストを実行します。
foreach ($this as $test) {
if ($result->shouldStop()) {
break;
}
if ($test instanceofPHPUnit_Framework_TestCase || $test instanceof self) {
$test->setbeStrictAboutChangesToGlobalState($this->beStrictAboutChangesToGlobalState);
$test->setBackupGlobals($this->backupGlobals);
$test->setBackupStaticAttributes($this->backupStaticAttributes);
$test->setRunTestInSeparateProcess($this->runTestInSeparateProcess);
}
$test->run($result);
}
TestCase.php
TestCaseのrun処理ではTestResultのrun処理を実行します。
if ($result === null) {
$result = $this->createResult();
}
...省略...
$result->run($this);
TestResult.php
TestResultのrun処理では、渡されたTestCaseのrunBare()を実行します。
$test->runBare();
TestCase.php
TestCaseのrunBare処理がテスト実行の最終地点で、before・after等のhookポイントを使いsetUp、テスト、tearDownを実行します。TestResultから呼び出しているのはテスト結果をTestResultが処理する為でしょう。実行後は順次TestResultが処理の呼び出し元に順に返され処理が終了します。
foreach ($hookMethods['before'] as $method) {
$this->$method();
}
$this->assertPreConditions();
$this->testResult = $this->runTest();
$this->verifyMockObjects();
$this->assertPostConditions();
かなりざっくりではありますがphpunitのメイン処理を確認してみました。だいたいの処理の流れは分かったので次回はモックオブジェクトを見ていこうと思います。