PHPUnitの使い方を仕組みから理解する


20161019203514

ここ数年仕事ではPHPを使って開発をしていますが、最近品質について考える機会が増えたこともあり、これを機にPHPUnitと周辺のモジュールの仕組みを理解してより楽にテストができるようにしたいと思います。

PHPUnitは?

Sebastian Bergmann

Created PHPUnit. Co-Founded thePHP.cc. Helps PHP developers build better software.

PHPUnitの作者は、Sebastian Bergmannという方でthePHP.ccのファウンダーのようです。関連情報は以下にて確認してみてください。

PHPUnitの構成

PHPUnitは以下のような構成になっています。

これらは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のメイン処理

20161019200553

20161019195137

20161019195138

PHPUnitでテストを実行するときのメイン処理をざくっと見てみます。

PHPUnit_TextUI_Command
PHPUnit_TextUI_TestRunner
TextUIのCommandとTestRunnerはphpunitのエントリポイントでphpunit実行時CommandからTestRunnerを呼び出し処理を開始します。
PHPUnit_Framework_Test(interface)
PHPUnit_Framework_TestSuite
TestRunnerはTestSuiteを作成しTestSuiteのメイン処理であるrun処理を実行します。

PHPUnit_Framework_Test(interface)
PHPUnit_Framework_Assert
PHPUnit_Framework_TestCase
PHPUnit_Framework_TestResult

TestSuiteはforeachにて順次TestCaseのテストを実行します。また、TestCaseの親クラスにはAssertがいてアサーションができるようになっています。

テストを実行する該当ソースをチェックする

TestSuite.php

TestRunnerから呼び出されたTestSuiteは、run処理にて対象テストをforeachして順次テストを実行します。

foreach ($this as $test) {
if ($result->shouldStop()) {
break;
}
if ($test instanceof PHPUnit_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のメイン処理を確認してみました。だいたいの処理の流れは分かったので次回はモックオブジェクトを見ていこうと思います。


カテゴリー: PHP

関連記事