taisablog

taisa's engineer blog

PHP

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

投稿日:December 5, 2016 更新日:

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は以下のような構成になっています。

  • phpunit
    • php-code-coverage
    • php-file-iterator
    • php-text-template
    • php-timer
    • php-token-stream
    • phpunit
    • phpunit-mock-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のメイン処理

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
-,

執筆者:

関連記事

PHPライブラリをPackagistに登録する方法

PHPのライブラリをPackagistに登録する方法を書いておきます。PackagistはPHPのパッケージリポジトリで、登録しておくとcomposerを使ってプロジェクトへインストールすることができます。ここではとあるプロジェクトをPackagistに登録する前提の流れで進めていきます。 Packagistに登録するプロジェクトを作成する 新規でプロジェクトを作成しcomposer initを実行します。 mkdir amazon-photo-formatter cd amazon-photo-formatter composer init composer initを実行すると色々と聞かれるので順番に進めていきます。まずはパッケージ名が聞かれます。<vendor>にはGitHubのアカウント名を指定し、<name>にはライブラリ名を記載します。ここではtaisa831/amazon-photo-formatterと記載しました。 Package name (/) [taisa831/packagist]: Descriptionはライブラリについての説明文なので、Format amazon photo file name to amazon photo’s format.と書きました。その他についてもサジェストされている内容とするか必要な内容を決めて進めていきます。 Description []: Author [Masaki Sato , n to skip]: Minimum Stability []: Package Type (e.g. library, project, metapackage, composer-plugin) []: library License []: MIT 次にこのライブラリが依存しているものがあればこの時点で指定することができます(後から手動で記載することも可能)。ここではphpunitを利用するのでrequire-devでphpunitを指定しました。 Would you like to define your dependencies (require) interactively [yes]? no Would you like to define your dev dependencies (require-dev) interactively [yes]? Search for a package: phpunit Found 15 packages matching phpunit [0] phpunit/phpunit [1] phpunit/phpunit-mock-objects Abandoned. Use instead. [2] phpunit/php-token-stream [3] phpunit/php-timer [4] phpunit/php-text-template [5] phpunit/php-file-iterator [6] phpunit/php-code-coverage [7] …

GitLabのprivateなPHPライブラリをcomposer installするには

社内ツールでprivateなリポジトリに置いておきたいけど、いろんなプロジェクトでcomposer installしたいというケースは以外とあるんじゃないかと思います。そういう時は、composer.jsonにrepositoriesを追加して、GitLab(ここではGitLabとしています)のURLを指定するとインストールが可能になります。しかしそのままだとpublicなリポジトリしかだめですが、privateなリポジトリであれば、GitLabからPersonal AcessTokenを取得して、composer config –global –auth gitlab-token.gitlab.com [ACESS_TOKEN]を実行すればcomposer installが可能になります。 { “name”: “taisa831/sample-framework-app”, “license”: “MIT”, “authors”: [ { “name”: “taisa”, “email”: “g5.taisa831@gmail.com” } ], “require”: { “taisa831/sample-framework”: “dev-master” }, “repositories”: [ { “type”: “vcs”, “url”: “git@gitlab.com:taisa831/sample-framework.git” } ] } では、Webフレームワークをprivateなリポジトリに公開して利用するところまでをやってみます。 (今回は便宜上publicにしています) 事前準備 ここではサンプルのWebフレームワーク(実装なし)をプロジェクトにインストールできるようにすることにします。リポジトリは2つで、フレームワークの実態であるsample-frameworkとフレームワークの雛形となるsample-framework-appを用意しておきました。それぞれの構成は以下の通りです。 https://gitlab.com/taisa831/sample-framework.git # フレームワークの実体 . ├── README.md ├── composer.json ├── src ├── tests └── vendor https://gitlab.com/taisa831/sample-framework-app.git # フレームワークの雛形 . ├── README.md ├── composer.json ├── composer.lock ├── config ├── controllers ├── models ├── public │   └── index.php ├── routes ├── tests └── views このsample-framework-appのcomposer.jsonには上記でも記載した内容が書かれています。requireにtaisa831/sample-frameworkを指定し、repositoriesにGitLabのURLを指定することで探してくれるようになります。 { “name”: “taisa831/sample-framework-app”, “license”: “MIT”, “authors”: [ { “name”: “taisa”, “email”: “g5.taisa831@gmail.com” } ], “require”: { …

PHPのモッキンフレームワークPhakeの使い方

前回に引き続きPHPUnit関連の記事です。今回はPHPのモッキンフレームワークであるPhakeの使い方を確認します。 Phakeについて 作者は? Mike Livelyという方のようです。 twitterGitHub Phakeって? こちらですね。 PhakeGitHub ファイル構成 一応ファイル構成を載せておきます。 . ├── Phake │   ├── Annotation │   │   ├── MockInitializer.php │   │   └── Reader.php │   ├── CallRecorder │   │   ├── Call.php │   │   ├── CallExpectation.php │   │   ├── CallInfo.php │   │   ├── IVerificationFailureHandler.php │   │   ├── IVerifierMode.php │   │   ├── OrderVerifier.php │   │   ├── Position.php │   │   ├── Recorder.php │   │   ├── Verifier.php │   │   ├── VerifierMode │   │   │   ├── AtLeast.php │   │   │   ├── AtMost.php │   │   │   ├── Result.php │   │   │   └── Times.php │   │   └── VerifierResult.php │   ├── ClassGenerator │   │   ├── EvalLoader.php │   …

PHPによるDBUnit超入門

例えば簡単なWebサービスでMVCのフレームワークを使っていてビジネスロジックを書く用にコントローラとモデルの間にサービス層を追加して開発している場合、コントローラやサービスはモックを駆使しながらテストを書いていくことができます。ただ、例えばフレームワークをバージョンアップしたい、PHPをバージョンアップしたいなどの場合に既存のモデル層に影響がないかをテストで確認したいなんてことがあります。そのような場合には、DBUnitを導入してみてもいいかもしれません。ということで本記事ではPHPによるDBUnitの使い方を書いてみます。 事前情報 今phpunit/dbunitをインストールしようとすると以下の文言が出力されます。詳しくはこちらのissueに書いてありますが、どうもSebastianさんはdbunitのメンテナンスをやめるようです。ただそれを受けてforkしたプロジェクトが出てきているようなので大丈夫かと思います。今回はSebastianさんの純正dbunitを使っています。 Package phpunit/dbunit is abandoned, you should avoid using it. No replacement was suggested また、DBUnitに関する詳しい情報はマニュアルにありますのでご確認ください。https://phpunit.de/manual/6.5/ja/database.html#database.implementing-getdataset 作成したサンプルプロジェクト 今回は、dbunitの確認だけをしたいので、dietcakeのmessage-boardというサンプルプロジェクトを利用しました。今回作成したDBUnit用のサンプルプロジェクトは GitHub からダウンロードして確認できます。 git clone git@github.com:taisa831/phpunit-dbunit-sample.git cd phpunit-dbunit-sample composer install # mysqlサーバを立て`app/config/sql/board.sql`を実行する(SQLは下記に記載しています) # テスト実行 ./vendor/bin/phpunit PHPUnit 7.5.8 by Sebastian Bergmann and contributors. ….. 5 / 5 (100%) Time: 207 ms, Memory: 4.00 MB OK (5 tests, 14 assertions) アプリ用のDDLです。開発用DBとは違うのでboard_dbunitというテーブル名にしています。 — — — Create database — CREATE DATABASE IF NOT EXISTS board_dbunit; GRANT SELECT, INSERT, UPDATE, DELETE ON board.* TO board_root@localhost IDENTIFIED BY ‘board_root’; FLUSH PRIVILEGES; — — Create tables — USE board_dbunit; CREATE TABLE IF NOT EXISTS thread ( id INT UNSIGNED …

PHPUnitのモックオブジェクトの使い方を仕組みから理解する

前回はPHPUnitのメイン処理を確認しました。今回はPHPUnitデフォルトのモックオブジェクトの仕組みを確認してみます。公式ドキュメントでは、第9章 テストダブルが該当箇所となります。 PHPUnitのモックオブジェクトについて PHPUnitは以下のような構成ですが、その中の「phpunit-mock-objects」がPHPUnitデフォルトのモックライブラリとなります。 phpunitphp-code-coveragephp-file-iteratorphp-text-templatephp-timerphp-token-streamphpunitphpunit-mock-objects ← これ 構成 PHPUnitモックオブジェクトのファイル構成は以下の通りです。 ├── Builder │   ├── Identity.php │   ├── InvocationMocker.php │   ├── Match.php │   ├── MethodNameMatch.php │   ├── Namespace.php │   ├── ParametersMatch.php │   └── Stub.php ├── Exception │   ├── BadMethodCallException.php │   ├── Exception.php │   └── RuntimeException.php ├── Generator │   ├── deprecation.tpl.dist │   ├── 省略… ├── Generator.php ├── Invocation │   ├── Object.php │   └── Static.php ├── Invocation.php ├── InvocationMocker.php ├── Invokable.php ├── Matcher │   ├── AnyInvokedCount.php │   ├── AnyParameters.php │   ├── ConsecutiveParameters.php │   ├── Invocation.php │   ├── InvokedAtIndex.php │   ├── InvokedAtLeastCount.php │   ├── InvokedAtLeastOnce.php │   ├── InvokedAtMostCount.php │   ├── InvokedCount.php │   ├── InvokedRecorder.php │   …