PHPUnitマニュアル

WBOY
リリース: 2016-06-23 13:14:07
オリジナル
2274 人が閲覧しました

PHPUnit マニュアル

Sebastian Bergmann

著作権 © 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 、201 5 セバスチャン バーグマン

この作品はクリエイティブ コモンズ表示の下でライセンスされています3.0 非移植の認可に関する合意。

このバージョンはPHPUnit 5.2に対応しています。最終更新日は 2016 年 3 月 15 日です。

PHPUnit PHAR 配布パッケージを確認する ファイル システムを使用してテスト スイートを調整する XML 構成を使用してテスト スイートを調整する 7. 未完了のテストとスキップされたテスト データベース テスト サポートされているベンダー PHPUnit データベース テスト ケース (DDL) の構成データベース アーキテクチャ 何をすべきか?ヒント: 独自の抽象データベース TestCase クラスを使用する DataSet と DataTable を理解する テーブル内のデータ行の数についてアサーションを作成する 複数のテーブルの状態についてアサーションを作成する PHPUnit はテストごとにデータベースを (再) 作成しますか?データベース拡張モジュールが正しく動作するには、アプリケーションで PDO を使用する必要がありますか? 「接続が多すぎます」エラーが表示された場合はどうすればよいですか?フラット XML/CSV データセットで NULL を処理するにはどうすればよいですか?モックオブジェクト (Mock Object) は、特性 (Traits) と抽象クラス、スタブ、または Web サービス (Web サービス) を模倣します。 13. ロギング (ロギング) PHPUnit_Framework_TestCase からサブクラスを派生して、PHPUnit_Framework_TestListener を実装します。 PHPUnit_Extensions_TestDecorator からサブクラスを派生して、PHPUnit_Framework_TestassertClassHasAttribute ( )assertClassHasStaticAttribute( )assertContainsOnlyInstancesOf()assertEqualXMLStructure()assertGreaterThanOrEqual()assertJsonFileEqualsJsonFile()assertJsonStringEqualsJsonFile()assertJsonStringEqualsJsonString()assertLessThanOrEqual()assertObjectHasAttribute()assertStringMatchesFormat()assertStringMatchesFormatFile ()assertStringEndsWith()assertStringEqualsFile()assertStringStartsWith()assertXmlFileEqualsXmlFile()assertXmlStringEqualsXmlFile()assertXmlStringEqualsXmlString()@ backStaticAttributes@expectedExceptionCode@expectedExceptionMessage@expectedExceptionMessageRegExp@runTestsInSeparateProcesses@runInSeparateProcess コード カバレッジ セットアップ用のファイルのホワイトリスト化 PHP INI 設定、定数、Selenium RC のグローバル変数 ブラウザーの構成

第 1 章 PHPUnit のインストール

PHPUnit 5.2 には、最新バージョンの PHP 5.6 が必要です。 PHP を強くお勧めします

PHPUnit では、通常、デフォルトで有効になっている dom および json 拡張機能を使用する必要があります。

PHPUnit には、pcre、reflection、spl 拡張機能も必要です。これらの標準拡張機能はデフォルトで有効になっており、PHP のビルド システムと C ソース コードを変更しない限り無効にすることはできません。

コードカバレッジ分析レポート機能には、Xdebug (2.2.1 以降) とトークナイザー拡張機能が必要です。 XML 形式でレポートを生成するには、xmlwriter 拡張機能が必要です。

PHP アーカイブ パッケージ (PHAR)

PHPUnit を入手するには、PHPUnit の PHP アーカイブ パッケージ (PHAR) をダウンロードするのが最も簡単な方法です。これには、PHPUnit に必要なすべてのコンポーネント (およびいくつかのオプション コンポーネント) がバンドルされています。単一のファイル:

PHP アーカイブ パッケージ (PHAR) を使用するには、phar 拡張子が必要です。

PHAR の --self-update 関数を使用するには、openssl 拡張機能が必要です。

Suhosin 拡張機能が有効になっている場合は、php.ini で PHAR の実行を許可する必要があります:

suhosin.executor.include.whitelist = phar
ログイン後にコピー

https://phar.phpunit.de/ からダウンロードするには、TLS/SNI をサポートするクライアントが必要です、wget 1.14 (またはそれ以降) など。

PHAR をグローバルにインストールする場合:

$ <strong>wget https://phar.phpunit.de/phpunit.phar</strong>$ <strong>chmod +x phpunit.phar</strong>$ <strong>sudo mv phpunit.phar /usr/local/bin/phpunit</strong>$ <strong>phpunit --version</strong>PHPUnit x.y.z by Sebastian Bergmann and contributors.
ログイン後にコピー

ダウンロードした PHAR ファイルを直接使用することもできます:

$ <strong>wget https://phar.phpunit.de/phpunit.phar</strong>$ <strong>php phpunit.phar --version</strong>PHPUnit x.y.z by Sebastian Bergmann and contributors.
ログイン後にコピー

Windows

一般に、Windows に PHAR をインストールするのは、Windows に Composer を手動でインストールするのと同じプロセスです:

  1. C:bin

    などの PHP バイナリ実行可能ファイル用のディレクトリを作成します
  2. ;C:bin
  3. を PATH 環境変数に追加します (関連ヘルプ)

    https://phar.phpunit をダウンロードします。 .de/phpunit.phar に移動し、ファイルを C:binphpunit.phar に保存します
  4. コマンド ラインを開きます (たとえば、
  5. Windows
  6. +

    R» Enter cmd » ENTER)

    外側のラッパー バッチ スクリプトをビルドします (最終的に C:binphpunit.cmd を取得します):
  7. C:\Users\username> <strong>cd C:\bin</strong>C:\bin> <strong>echo @php "%~dp0phpunit.phar" %* > phpunit.cmd</strong>C:\bin> <strong>exit</strong>
    ログイン後にコピー
  8. 新しいコマンド ライン ウィンドウを開き、PHPUnit が任意のパスで実行できることを確認します:
  9. C:\Users\username> <strong>phpunit --version</strong>PHPUnit x.y.z by Sebastian Bergmann and contributors.
    ログイン後にコピー
  10. Cygwin または MingW32 (TortoiseGit など) の場合) シェル環境では、ステップ 5 をスキップできます。 代わりに、ファイルを phpunit (拡張子 .phar なし) として保存し、
  11. chmod 775 phpunit
で実行可能にします。

校验 PHPUnit PHAR 发行包

由 PHPUnit 项目分发的所有官方代码发行包都由发行包管理器进行签名。在phar.phpunit.de 上有 PGP 签名和 SHA1 散列值可用于校验。

下面的例子详细说明了如何对发行包进行校验。首先下载 phpunit.phar和与之对应的单独 PGP 签名 phpunit.phar.asc:

<strong>wget https://phar.phpunit.de/phpunit.phar</strong><strong>wget https://phar.phpunit.de/phpunit.phar.asc</strong>
ログイン後にコピー

用单独的签名( phpunit.phar)对 PHPUnit 的 PHP 档案包( phpunit.phar.asc)进行校验:

<strong>gpg phpunit.phar.asc</strong>gpg: Signature made Sat 19 Jul 2014 01:28:02 PM CEST using RSA key ID 6372C20Agpg: Can't check signature: public key not found
ログイン後にコピー

在本地系统中没有发行包管理器的公钥( 6372C20A)。为了能进行校验,必须从某个密钥服务器上取得发行包管理器的公钥。其中一个服务器是 pgp.uni-mainz.de。所有密钥服务器是链接在一起的,因此连接到任一密钥服务器都可以。

<strong>gpg --keyserver pgp.uni-mainz.de --recv-keys 0x4AA394086372C20A</strong>gpg: requesting key 6372C20A from hkp server pgp.uni-mainz.degpg: key 6372C20A: public key "Sebastian Bergmann <sb@sebastian-bergmann.de>" importedgpg: Total number processed: 1gpg:               imported: 1  (RSA: 1)
ログイン後にコピー

现在已经取得了条目名称为"Sebastian Bergmann "的公钥。不过无法检验这个密钥确实是由名叫 Sebastian Bergmann 的人创建的。但是可以先试着校验发行包的签名:

<strong>gpg phpunit.phar.asc</strong>gpg: Signature made Sat 19 Jul 2014 01:28:02 PM CEST using RSA key ID 6372C20Agpg: Good signature from "Sebastian Bergmann <sb@sebastian-bergmann.de>"gpg:                 aka "Sebastian Bergmann <sebastian@php.net>"gpg:                 aka "Sebastian Bergmann <sebastian@thephp.cc>"gpg:                 aka "Sebastian Bergmann <sebastian@phpunit.de>"gpg:                 aka "Sebastian Bergmann <sebastian.bergmann@thephp.cc>"gpg:                 aka "[jpeg image of size 40635]"gpg: WARNING: This key is not certified with a trusted signature!gpg:          There is no indication that the signature belongs to the owner.Primary key fingerprint: D840 6D0D 8294 7747 2937  7831 4AA3 9408 6372 C20A
ログイン後にコピー

此时,签名已经没问题了,但是这个公钥还不能信任。签名没问题意味着文件未被篡改。可是由于公钥加密系统的性质,还需要再校验密钥 6372C20A确实是由真正的 Sebastian Bergmann 创建的。

任何攻击者都能创建公钥并将其上传到公钥服务器。他们可以建立一个带恶意的发行包,并用这个假密钥进行签名。这样,如果尝试对这个损坏了的发行包进行签名校验,由于密钥是“真”密钥,校验将成功完成。因此,需要对这个密钥的真实性进行校验。如何对公钥的真实性进行校验已经超出了本文档的范畴。

有个比较谨慎的做法是创建一个脚本来管理 PHPUnit 的安装,在运行测试套件之前校验 GnuPG 签名。例如:

#!/usr/bin/env bashclean=1 # 是否在测试完成之后删除 phpunit.phar ?aftercmd="php phpunit.phar --bootstrap bootstrap.php src/tests"gpg --fingerprint D8406D0D82947747293778314AA394086372C20Aif [ $? -ne 0 ]; then    echo -e "\033[33mDownloading PGP Public Key...\033[0m"    gpg --recv-keys D8406D0D82947747293778314AA394086372C20A    # Sebastian Bergmann <sb@sebastian-bergmann.de>    gpg --fingerprint D8406D0D82947747293778314AA394086372C20A    if [ $? -ne 0 ]; then        echo -e "\033[31mCould not download PGP public key for verification\033[0m"        exit    fifiif [ "$clean" -eq 1 ]; then    # 如果存在就清理掉    if [ -f phpunit.phar ]; then        rm -f phpunit.phar    fi    if [ -f phpunit.phar.asc ]; then        rm -f phpunit.phar.asc    fifi# 抓取最新的发行版和对应的签名if [ ! -f phpunit.phar ]; then    wget https://phar.phpunit.de/phpunit.pharfiif [ ! -f phpunit.phar.asc ]; then    wget https://phar.phpunit.de/phpunit.phar.ascfi# 在运行前先校验gpg --verify phpunit.phar.asc phpunit.pharif [ $? -eq 0 ]; then    echo    echo -e "\033[33mBegin Unit Testing\033[0m"    # 运行测试套件    `$after_cmd`    # 清理    if [ "$clean" -eq 1 ]; then        echo -e "\033[32mCleaning Up!\033[0m"        rm -f phpunit.phar        rm -f phpunit.phar.asc    fielse    echo    chmod -x phpunit.phar    mv phpunit.phar /tmp/bad-phpunit.phar    mv phpunit.phar.asc /tmp/bad-phpunit.phar.asc    echo -e "\033[31mSignature did not match! PHPUnit has been moved to /tmp/bad-phpunit.phar\033[0m"    exit 1fi
ログイン後にコピー

Composer

如果用 Composer来管理项目的依赖关系,只要在项目的 composer.json文件中简单地加上对 phpunit/phpunit的依赖关系即可。下面是一个最小化的 composer.json文件的例子,只定义了一个对 PHPUnit 5.0 的开发时(development-time)依赖:

{    "require-dev": {        "phpunit/phpunit": "5.0.*"    }}
ログイン後にコピー

要通过 Composer 完成系统级的安装,可以运行:

<strong>composer global require "phpunit/phpunit=5.0.*"</strong>
ログイン後にコピー

请确保 path 变量中包含有 ~/.composer/vendor/bin/。

可选的组件包

有以下可选组件包可用:

PHP_Invoker

一个工具类,可以用带有超时限制的方式调用可调用内容。当需要在严格模式下保证测试的超时限制时,这个组件包是必须的。

PHPUnit 的 PHAR 分发中已经包含了此组件包。若要通过 Composer 安装此组件包,添加如下 "require-dev"依赖项:

<strong>"phpunit/php-invoker": "*"</strong>
ログイン後にコピー

DbUnit

移植到 PHP/PHPUnit 上的 DbUnit 用于提供对数据库交互测试的支持。

PHPUnit 的 PHAR 分发中已经包含了此组件包。若要通过 Composer 安装此组件包,添加如下 "require-dev"依赖项:

<strong>"phpunit/dbunit": ">=1.2"</strong>
ログイン後にコピー

第 2 章 编写 PHPUnit 测试

展示了如何用 PHPUnit 编写测试来对 PHP 数组操作进行测试。本例介绍了用 PHPUnit 编写测试的基本惯例与步骤:

  1. 针对类 Class的测试写在类 ClassTest中。

  2. ClassTest(通常)继承自 PHPUnit_Framework_TestCase。

  3. 测试都是命名为 test*的公用方法。

    也可以在方法的文档注释块(docblock)中使用 @test标注将其标记为测试方法。

  4. 在测试方法内,类似于 assertEquals()(参见)这样的断言方法用来对实际值与预期值的匹配做出断言。

例 2.1: 用 PHPUnit 测试数组操作

<?phpclass StackTest extends PHPUnit_Framework_TestCase{    public function testPushAndPop()    {        $stack = array();        $this->assertEquals(0, count($stack));        array_push($stack, 'foo');        $this->assertEquals('foo', $stack[count($stack)-1]);        $this->assertEquals(1, count($stack));        $this->assertEquals('foo', array_pop($stack));        $this->assertEquals(0, count($stack));    }}?>
ログイン後にコピー

当你想把一些东西写到 print语句或者调试表达式中时,别这么做,将其写成一个测试来代替。

-- Martin Fowler

テストされた依存関係

単体テストは主に良い実践として書かれており、開発者がバグを特定して修正し、コードをリファクタリングするのに役立ち、テスト対象のソフトウェアユニットのドキュメントとみなすこともできます。これらの利点を実現するには、理想的な単体テストはプログラム内のすべての可能なパスをカバーする必要があります。単体テストは通常​​、関数またはメソッド内の特定のパスをカバーします。ただし、テスト メソッドは適切にカプセル化された独立したエンティティである必要はありません。テスト実装には、テスト メソッド間に暗黙的な依存関係が隠されていることがよくあります。

-- エイドリアン・クーン 他

PHPUnit支持对测试方法之间的显式依赖关系进行声明。这种依赖关系并不是定义在测试方法的执行顺序中,而是允许生产者(producer)返回一个测试基境(fixture)的实例,并将此实例传递给依赖于它的消费者(consumer)们。

  • 生产者(producer),是能生成被测单元并将其作为返回值的测试方法。

  • 消费者(consumer),是依赖于一个或多个生产者及其返回值的测试方法。

展示了如何用 @depends标注来表达测试方法之间的依赖关系。

例 2.2: 用 @depends标注来表达依赖关系

<?phpclass StackTest extends PHPUnit_Framework_TestCase{    public function testEmpty()    {        $stack = array();        $this->assertEmpty($stack);        return $stack;    }    /**     * @depends testEmpty     */    public function testPush(array $stack)    {        array_push($stack, 'foo');        $this->assertEquals('foo', $stack[count($stack)-1]);        $this->assertNotEmpty($stack);        return $stack;    }    /**     * @depends testPush     */    public function testPop(array $stack)    {        $this->assertEquals('foo', array_pop($stack));        $this->assertEmpty($stack);    }}?>
ログイン後にコピー

在上例中,第一个测试, testEmpty(),创建了一个新数组,并断言其为空。随后,此测试将此基境作为结果返回。第二个测试, testPush(),依赖于 testEmpty(),并将所依赖的测试之结果作为参数传入。最后, testPop()依赖于 testPush()。

注意

默认情况下,生产者所产生的返回值将“原样”传递给相应的消费者。这意味着,如果生产者返回的是一个对象,那么传递给消费者的将是一个指向此对象的引用。如果需要传递对象的副本而非引用,则应当用 @depends clone替代 @depends。

为了快速定位缺陷,我们希望把注意力集中于相关的失败测试上。这就是为什么当某个测试所依赖的测试失败时,PHPUnit 会跳过这个测试。通过利用测试之间的依赖关系,缺陷定位得到了改进,如中所示。

例 2.3: 利用测试之间的依赖关系

<?phpclass DependencyFailureTest extends PHPUnit_Framework_TestCase{    public function testOne()    {        $this->assertTrue(FALSE);    }    /**     * @depends testOne     */    public function testTwo()    {    }}?>
ログイン後にコピー
<strong>phpunit --verbose DependencyFailureTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors.FSTime: 0 seconds, Memory: 5.00MbThere was 1 failure:1) DependencyFailureTest::testOneFailed asserting that false is true./home/sb/DependencyFailureTest.php:6There was 1 skipped test:1) DependencyFailureTest::testTwoThis test depends on "DependencyFailureTest::testOne" to pass.FAILURES!Tests: 1, Assertions: 1, Failures: 1, Skipped: 1.
ログイン後にコピー

测试可以使用多个 @depends标注。PHPUnit 不会更改测试的运行顺序,因此你需要自行保证某个测试所依赖的所有测试均出现于这个测试之前。

拥有多个 @depends标注的测试,其第一个参数是第一个生产者提供的基境,第二个参数是第二个生产者提供的基境,以此类推。参见

例 2.4: 有多重依赖的测试

<?phpclass MultipleDependenciesTest extends PHPUnit_Framework_TestCase{    public function testProducerFirst()    {        $this->assertTrue(true);        return 'first';    }    public function testProducerSecond()    {        $this->assertTrue(true);        return 'second';    }    /**     * @depends testProducerFirst     * @depends testProducerSecond     */    public function testConsumer()    {        $this->assertEquals(            array('first', 'second'),            func_get_args()        );    }}?>
ログイン後にコピー
<strong>phpunit --verbose MultipleDependenciesTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors....Time: 0 seconds, Memory: 3.25MbOK (3 tests, 3 assertions)
ログイン後にコピー

数据供给器

测试方法可以接受任意参数。这些参数由数据供给器方法(在中,是 additionProvider()方法)提供。用 @dataProvider标注来指定使用哪个数据供给器方法。

数据供给器方法必须声明为 public,其返回值要么是一个数组,其每个元素也是数组;要么是一个实现了 Iterator接口的对象,在对它进行迭代时每步产生一个数组。每个数组都是测试数据集的一部分,将以它的内容作为参数来调用测试方法。

例 2.5: 使用返回数组的数组的数据供给器

<?phpclass DataTest extends PHPUnit_Framework_TestCase{    /**     * @dataProvider additionProvider     */    public function testAdd($a, $b, $expected)    {        $this->assertEquals($expected, $a + $b);    }    public function additionProvider()    {        return array(          array(0, 0, 0),          array(0, 1, 1),          array(1, 0, 1),          array(1, 1, 3)        );    }}?>
ログイン後にコピー
<strong>phpunit DataTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors....FTime: 0 seconds, Memory: 5.75MbThere was 1 failure:1) DataTest::testAdd with data set #3 (1, 1, 3)Failed asserting that 2 matches expected 3./home/sb/DataTest.php:9FAILURES!Tests: 4, Assertions: 4, Failures: 1.
ログイン後にコピー

当使用到大量数据集时,最好逐个用字符串键名对其命名,避免用默认的数字键名。这样输出信息会更加详细些,其中将包含打断测试的数据集所对应的名称。

例 2.6: 使用带有命名数据集的数据供给器

<?phpclass DataTest extends PHPUnit_Framework_TestCase{    /**     * @dataProvider additionProvider     */    public function testAdd($a, $b, $expected)    {        $this->assertEquals($expected, $a + $b);    }    public function additionProvider()    {        return array(          'adding zeros' => array(0, 0, 0),          'zero plus one' => array(0, 1, 1),          'one plus zero' => array(1, 0, 1),          'one plus one' => array(1, 1, 3)        );    }}?>
ログイン後にコピー
<strong>phpunit DataTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors....FTime: 0 seconds, Memory: 5.75MbThere was 1 failure:1) DataTest::testAdd with data set "one plus one" (1, 1, 3)Failed asserting that 2 matches expected 3./home/sb/DataTest.php:9FAILURES!Tests: 4, Assertions: 4, Failures: 1.
ログイン後にコピー

例 2.7: 使用返回迭代器对象的数据供给器

<?phprequire 'CsvFileIterator.php';class DataTest extends PHPUnit_Framework_TestCase{    /**     * @dataProvider additionProvider     */    public function testAdd($a, $b, $expected)    {        $this->assertEquals($expected, $a + $b);    }    public function additionProvider()    {        return new CsvFileIterator('data.csv');    }}?>
ログイン後にコピー
<strong>phpunit DataTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors....FTime: 0 seconds, Memory: 5.75MbThere was 1 failure:1) DataTest::testAdd with data set #3 ('1', '1', '3')Failed asserting that 2 matches expected '3'./home/sb/DataTest.php:11FAILURES!Tests: 4, Assertions: 4, Failures: 1.
ログイン後にコピー

例 2.8: CsvFileIterator 类

<?phpclass CsvFileIterator implements Iterator {    protected $file;    protected $key = 0;    protected $current;    public function __construct($file) {        $this->file = fopen($file, 'r');    }    public function __destruct() {        fclose($this->file);    }    public function rewind() {        rewind($this->file);        $this->current = fgetcsv($this->file);        $this->key = 0;    }    public function valid() {        return !feof($this->file);    }    public function key() {        return $this->key;    }    public function current() {        return $this->current;    }    public function next() {        $this->current = fgetcsv($this->file);        $this->key++;    }}?>
ログイン後にコピー

如果测试同时从 @dataProvider方法和一个或多个 @depends测试接收数据,那么来自于数据供给器的参数将先于来自所依赖的测试的。来自于所依赖的测试的参数对于每个数据集都是一样的。参见

例 2.9: 在同一个测试中组合使用 @depends 和 @dataProvider

<?phpclass DependencyAndDataProviderComboTest extends PHPUnit_Framework_TestCase{    public function provider()    {        return array(array('provider1'), array('provider2'));    }    public function testProducerFirst()    {        $this->assertTrue(true);        return 'first';    }    public function testProducerSecond()    {        $this->assertTrue(true);        return 'second';    }    /**     * @depends testProducerFirst     * @depends testProducerSecond     * @dataProvider provider     */    public function testConsumer()    {        $this->assertEquals(            array('provider1', 'first', 'second'),            func_get_args()        );    }}?>
ログイン後にコピー
<strong>phpunit --verbose DependencyAndDataProviderComboTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors....FTime: 0 seconds, Memory: 3.50MbThere was 1 failure:1) DependencyAndDataProviderComboTest::testConsumer with data set #1 ('provider2')Failed asserting that two arrays are equal.--- Expected+++ Actual@@ @@Array (-    0 => 'provider1'+    0 => 'provider2'1 => 'first'2 => 'second')/home/sb/DependencyAndDataProviderComboTest.php:31FAILURES!Tests: 4, Assertions: 4, Failures: 1.
ログイン後にコピー

注意

如果一个测试依赖于另外一个使用了数据供给器的测试,仅当被依赖的测试至少能在一组数据上成功时,依赖于它的测试才会运行。使用了数据供给器的测试,其运行结果是无法注入到依赖于此测试的其他测试中的。

注意

所有的数据供给器方法的执行都是在对 setUpBeforeClass静态方法的调用和第一次对 setUp方法的调用之前完成的。因此,无法在数据供给器中使用创建于这两个方法内的变量。这是必须的,这样 PHPUnit 才能计算测试的总数量。

对异常进行测试

展示了如何用 @expectException标注来测试被测代码中是否抛出了异常。

例 2.10: 使用 expectException() 方法

<?phpclass ExceptionTest extends PHPUnit_Framework_TestCase{    public function testException()    {        $this->expectException(InvalidArgumentException::class);    }}?>
ログイン後にコピー
<strong>phpunit ExceptionTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors.FTime: 0 seconds, Memory: 4.75MbThere was 1 failure:1) ExceptionTest::testExceptionExpected exception InvalidArgumentExceptionFAILURES!Tests: 1, Assertions: 1, Failures: 1.
ログイン後にコピー
ログイン後にコピー

除了 expectException()方法外,还有 expectExceptionCode()、 expectExceptionMessage()和 expectExceptionMessageRegExp()方法可以用于为被测代码所抛出的异常建立预期。

或者,也可以用 @expectedException、 @expectedExceptionCode、 @expectedExceptionMessage和 @expectedExceptionMessageRegExp标注来为被测代码所抛出的异常建立预期。展示了一个范例。

例 2.11: 使用 @expectedException 标注

<?phpclass ExceptionTest extends PHPUnit_Framework_TestCase{    /**     * @expectedException InvalidArgumentException     */    public function testException()    {    }}?>
ログイン後にコピー
<strong>phpunit ExceptionTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors.FTime: 0 seconds, Memory: 4.75MbThere was 1 failure:1) ExceptionTest::testExceptionExpected exception InvalidArgumentExceptionFAILURES!Tests: 1, Assertions: 1, Failures: 1.
ログイン後にコピー
ログイン後にコピー

对 PHP 错误进行测试

默认情况下,PHPUnit 将测试在执行中触发的 PHP 错误、警告、通知都转换为异常。利用这些异常,就可以,比如说,预期测试将触发 PHP 错误,如所示。

注意

PHP 的 error_reporting运行时配置会对 PHPUnit 将哪些错误转换为异常有所限制。如果在这个特性上碰到问题,请确认 PHP 的配置中没有抑制想要测试的错误类型。

例 2.12: 用 @expectedException 来预期 PHP 错误

<?phpclass ExpectedErrorTest extends PHPUnit_Framework_TestCase{    /**     * @expectedException PHPUnit_Framework_Error     */    public function testFailingInclude()    {        include 'not_existing_file.php';    }}?>
ログイン後にコピー
<strong>phpunit -d error_reporting=2 ExpectedErrorTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors..Time: 0 seconds, Memory: 5.25MbOK (1 test, 1 assertion)
ログイン後にコピー

PHPUnit_Framework_Error_Notice和 PHPUnit_Framework_Error_Warning分别代表 PHP 通知与 PHP 警告。

注意

对异常进行测试是越明确越好的。对太笼统的类进行测试有可能导致不良副作用。因此,不再允许用 @expectedException或 setExpectedException()对 Exception类进行测试。

如果测试依靠会触发错误的 PHP 函数,例如 fopen,有时候在测试中使用错误抑制符会很有用。通过抑制住错误通知,就能对返回值进行检查,否则错误通知将会导致抛出 PHPUnit_Framework_Error_Notice。

例 2.13: 对会引发PHP 错误的代码的返回值进行测试

<?phpclass ErrorSuppressionTest extends PHPUnit_Framework_TestCase{    public function testFileWriting() {        $writer = new FileWriter;        $this->assertFalse(@$writer->write('/is-not-writeable/file', 'stuff'));    }}class FileWriter{    public function write($file, $content) {        $file = fopen($file, 'w');        if($file == false) {            return false;        }        // ...    }}?>
ログイン後にコピー
<strong>phpunit ErrorSuppressionTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors..Time: 1 seconds, Memory: 5.25MbOK (1 test, 1 assertion)
ログイン後にコピー

如果不使用错误抑制符,此测试将会失败,并报告 fopen(/is-not-writeable/file): failed to open stream: No such file or directory。

对输出进行测试

有时候,想要断言(比如说)某方法的运行过程中生成了预期的输出(例如,通过 echo或 print)。 PHPUnit_Framework_TestCase类使用 PHP 的 输出缓冲特性来为此提供必要的功能支持。

展示了如何用 expectOutputString()方法来设定所预期的输出。如果没有产生预期的输出,测试将计为失败。

例 2.14: 对函数或方法的输出进行测试

<?phpclass OutputTest extends PHPUnit_Framework_TestCase{    public function testExpectFooActualFoo()    {        $this->expectOutputString('foo');        print 'foo';    }    public function testExpectBarActualBaz()    {        $this->expectOutputString('bar');        print 'baz';    }}?>
ログイン後にコピー
<strong>phpunit OutputTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors..FTime: 0 seconds, Memory: 5.75MbThere was 1 failure:1) OutputTest::testExpectBarActualBazFailed asserting that two strings are equal.--- Expected+++ Actual@@ @@-'bar'+'baz'FAILURES!Tests: 2, Assertions: 2, Failures: 1.
ログイン後にコピー

中列举了用于对输出进行测试的各种方法。

表 2.1. 用于对输出进行测试的方法

方法 含义
void expectOutputRegex(string $regularExpression) 设置输出预期为输出应当匹配正则表达式 $regularExpression。
void expectOutputString(string $expectedString) 设置输出预期为输出应当与 $expectedString字符串相等。
bool setOutputCallback(callable $callback) 设置回调函数,用来做诸如将实际输出规范化之类的动作。

注意

在严格模式下,本身产生输出的测试将会失败。

错误相关信息的输出

当有测试失败时,PHPUnit 全力提供尽可能多的有助于找出问题所在的上下文信息。

例 2.15: 数组比较失败时生成的错误相关信息输出

<?phpclass ArrayDiffTest extends PHPUnit_Framework_TestCase{    public function testEquality() {        $this->assertEquals(            array(1,2,3 ,4,5,6),            array(1,2,33,4,5,6)        );    }}?>
ログイン後にコピー
<strong>phpunit ArrayDiffTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors.FTime: 0 seconds, Memory: 5.25MbThere was 1 failure:1) ArrayDiffTest::testEqualityFailed asserting that two arrays are equal.--- Expected+++ Actual@@ @@ Array (     0 => 1     1 => 2-    2 => 3+    2 => 33     3 => 4     4 => 5     5 => 6 )/home/sb/ArrayDiffTest.php:7FAILURES!Tests: 1, Assertions: 1, Failures: 1.
ログイン後にコピー

在这个例子中,数组中只有一个值不同,但其他值也都同时显示出来,以提供关于错误发生的位置的上下文信息。

当生成的输出很长而难以阅读时,PHPUnit 将对其进行分割,并在每个差异附近提供少数几行上下文信息。

例 2.16: 长数组比较失败时生成的错误相关信息输出

<?phpclass LongArrayDiffTest extends PHPUnit_Framework_TestCase{    public function testEquality() {        $this->assertEquals(            array(0,0,0,0,0,0,0,0,0,0,0,0,1,2,3 ,4,5,6),            array(0,0,0,0,0,0,0,0,0,0,0,0,1,2,33,4,5,6)        );    }}?>
ログイン後にコピー
<strong>phpunit LongArrayDiffTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors.FTime: 0 seconds, Memory: 5.25MbThere was 1 failure:1) LongArrayDiffTest::testEqualityFailed asserting that two arrays are equal.--- Expected+++ Actual@@ @@     13 => 2-    14 => 3+    14 => 33     15 => 4     16 => 5     17 => 6 )/home/sb/LongArrayDiffTest.php:7FAILURES!Tests: 1, Assertions: 1, Failures: 1.
ログイン後にコピー

边缘情况

当比较失败时,PHPUnit 为输入值建立文本表示,然后以此进行对比。这种实现导致在差异指示中显示出来的问题可能比实际上存在的多。

这种情况只出现在对数组或者对象使用 assertEquals 或其他“弱”比较函数时。

例 2.17: 当使用弱比较时在生成的差异结果中出现的边缘情况

<?phpclass ArrayWeakComparisonTest extends PHPUnit_Framework_TestCase{    public function testEquality() {        $this->assertEquals(            array(1  ,2,3 ,4,5,6),            array('1',2,33,4,5,6)        );    }}?>
ログイン後にコピー
<strong>phpunit ArrayWeakComparisonTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors.FTime: 0 seconds, Memory: 5.25MbThere was 1 failure:1) ArrayWeakComparisonTest::testEqualityFailed asserting that two arrays are equal.--- Expected+++ Actual@@ @@ Array (-    0 => 1+    0 => '1'     1 => 2-    2 => 3+    2 => 33     3 => 4     4 => 5     5 => 6 )/home/sb/ArrayWeakComparisonTest.php:7FAILURES!Tests: 1, Assertions: 1, Failures: 1.
ログイン後にコピー

在这个例子中,第一个索引项中的 1and '1'在报告中被视为不同,虽然 assertEquals 认为这两个值是匹配的。

第 3 章 命令行测试执行器

PHPUnit 命令行测试执行器可通过 phpunit命令调用。下面的代码展示了如何用 PHPUnit 命令行测试执行器来运行测试:

<strong>phpunit ArrayTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors...Time: 0 secondsOK (2 tests, 2 assertions)
ログイン後にコピー

上面这个调用例子中,PHPUnit 命令行测试执行器将在当前工作目录中寻找 ArrayTest.php源文件并加载之。而在此源文件中应当能找到 ArrayTest测试用例类,此类中的测试将被执行。

对于每个测试的运行,PHPUnit 命令行工具输出一个字符来指示进展:

.

当测试成功时输出。

F

当测试方法运行过程中一个断言失败时输出。

E

当测试方法运行过程中产生一个错误时输出。

R

当测试被标记为有风险时输出(参见)。

S

当测试被跳过时输出(参见)。

I

当测试被标记为不完整或未实现时输出(参见)。

PHPUnit 区分 败(failure)与 错误(error)。失败指的是被违背了的 PHPUnit 断言,例如一个失败的 assertEquals()调用。错误指的是意料之外的异常(exception)或 PHP 错误。这种差异已被证明在某些时候是非常有用的,因为错误往往比失败更容易修复。如果得到了一个非常长的问题列表,那么最好先对付错误,当错误全部修复了之后再试一次瞧瞧还有没有失败。

命令行选项

让我们来瞧瞧以下代码中命令行测试运行器的各种选项:

<strong>phpunit --help</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors.Usage: phpunit [options] UnitTest [UnitTest.php]       phpunit [options] <directory>Code Coverage Options:  --coverage-clover <file>  Generate code coverage report in Clover XML format.  --coverage-crap4j <file>  Generate code coverage report in Crap4J XML format.  --coverage-html <dir>     Generate code coverage report in HTML format.  --coverage-php <file>     Export PHP_CodeCoverage object to file.  --coverage-text=<file>    Generate code coverage report in text format.                            Default: Standard output.  --coverage-xml <dir>      Generate code coverage report in PHPUnit XML format.Logging Options:  --log-junit <file>        Log test execution in JUnit XML format to file.  --log-tap <file>          Log test execution in TAP format to file.  --log-json <file>         Log test execution in JSON format.  --testdox-html <file>     Write agile documentation in HTML format to file.  --testdox-text <file>     Write agile documentation in Text format to file.Test Selection Options:  --filter <pattern>        Filter which tests to run.  --testsuite <pattern>     Filter which testsuite to run.  --group ...               Only runs tests from the specified group(s).  --exclude-group ...       Exclude tests from the specified group(s).  --list-groups             List available test groups.  --test-suffix ...         Only search for test in files with specified                            suffix(es). Default: Test.php,.phptTest Execution Options:  --report-useless-tests    Be strict about tests that do not test anything.  --strict-coverage         Be strict about unintentionally covered code.  --strict-global-state     Be strict about changes to global state  --disallow-test-output    Be strict about output during tests.  --enforce-time-limit      Enforce time limit based on test size.  --disallow-todo-tests     Disallow @todo-annotated tests.  --process-isolation       Run each test in a separate PHP process.  --no-globals-backup       Do not backup and restore $GLOBALS for each test.  --static-backup           Backup and restore static attributes for each test.  --colors=<flag>           Use colors in output ("never", "auto" or "always").  --columns <n>             Number of columns to use for progress output.  --columns max             Use maximum number of columns for progress output.  --stderr                  Write to STDERR instead of STDOUT.  --stop-on-error           Stop execution upon first error.  --stop-on-failure         Stop execution upon first error or failure.  --stop-on-risky           Stop execution upon first risky test.  --stop-on-skipped         Stop execution upon first skipped test.  --stop-on-incomplete      Stop execution upon first incomplete test.  -v|--verbose              Output more verbose information.  --debug                   Display debugging information during test execution.  --loader <loader>         TestSuiteLoader implementation to use.  --repeat <times>          Runs the test(s) repeatedly.  --tap                     Report test execution progress in TAP format.  --testdox                 Report test execution progress in TestDox format.  --printer <printer>       TestListener implementation to use.Configuration Options:  --bootstrap <file>        A "bootstrap" PHP file that is run before the tests.  -c|--configuration <file> Read configuration from XML file.  --no-configuration        Ignore default configuration file (phpunit.xml).  --include-path <path(s)>  Prepend PHP's include_path with given path(s).  -d key[=value]            Sets a php.ini value.Miscellaneous Options:  -h|--help                 Prints this usage information.  --version                 Prints the version and exits.
ログイン後にコピー
phpunit UnitTest

运行由 UnitTest类提供的测试。这个类应当在 UnitTest.php源文件中声明。

UnitTest这个类必须满足以下二个条件之一:要么它继承自 PHPUnit_Framework_TestCase;要么它提供 public static suite()方法,这个方法返回一个 PHPUnit_Framework_Test对象,比如,一个 PHPUnit_Framework_TestSuite类的实例。

phpunit UnitTest UnitTest.php

运行由 UnitTest类提供的测试。这个类应当在指定的源文件中声明。

--coverage-clover

为运行的测试生成带有代码覆盖率信息的 XML 格式的日志文件。更多细节请参见。

请注意,此功能仅当安装了 tokenizer 和 Xdebug 这两个 PHP 扩展后才可用。

--coverage-crap4j

生成 Crap4j 格式的代码覆盖率报告。更多细节请参见。

请注意,此功能仅当安装了 tokenizer 和 Xdebug 这两个 PHP 扩展后才可用。

--coverage-html

生成 HTML 格式的代码覆盖率报告。更多细节请参见。

请注意,此功能仅当安装了 tokenizer 和 Xdebug 这两个 PHP 扩展后才可用。

--coverage-php

生成一个序列化后的 PHP_CodeCoverage 对象,此对象含有代码覆盖率信息。

请注意,此功能仅当安装了 tokenizer 和 Xdebug 这两个 PHP 扩展后才可用。

--coverage-text

为运行的测试以人们可读的格式生成带有代码覆盖率信息的日志文件或命令行输出。更多细节请参见。

请注意,此功能仅当安装了 tokenizer 和 Xdebug 这两个 PHP 扩展后才可用。

--log-junit

为运行的测试生成 JUnit XML 格式的日志文件。更多细节请参见。

--log-tap

为运行的测试生成 Test Anything Protocol (TAP)格式的日志文件。更多细节请参见。

--log-json

生成 JSON格式的日志文件。更多细节请参见。

--testdox-html和 --testdox-text

为运行的测试以 HTML 或纯文本格式生成敏捷文档。更多细节请参见。

--filter

只运行名称与给定模式匹配的测试。如果模式未闭合包裹于分隔符,PHPUnit 将用 /分隔符对其进行闭合包裹。

测试名称将以以下格式之一进行匹配:

TestNamespace\TestCaseClass::testMethod

默认的测试名称格式等价于在测试方法内使用 __METHOD__魔术常量。

TestNamespace\TestCaseClass::testMethod with data set #0

当测试拥有数据供给器时,数据的每轮迭代都会将其当前索引附加在默认测试名称结尾处。

TestNamespace\TestCaseClass::testMethod with data set "my named data"

当测试拥有使用命名数据集的数据供给器时,数据的每轮迭代都会将当前名称附加在默认测试名称结尾处。命名数据集的例子参见。

例 3.1: 命名数据集

<?phpnamespace TestNamespace;class TestCaseClass extends \PHPUnit_Framework_TestCase{    /**     * @dataProvider provider     */    public function testMethod($data)    {        $this->assertTrue($data);    }    public function provider()    {        return array(           'my named data' => array(true),           'my data'       => array(true)        );    }}?>
ログイン後にコピー

/path/to/my/test.phpt

对于 PHPT 测试,其测试名称是文件系统路径。

有效的过滤器模式例子参见。

例 3.2: 过滤器模式例子

  • --filter 'TestNamespace\\TestCaseClass::testMethod'

  • --filter 'TestNamespace\\TestCaseClass'

  • --filter TestNamespace

  • --filter TestCaseClass

  • --filter testMethod

  • --filter '/::testMethod .*"my named data"/'

  • --filter '/::testMethod .*#5$/'

  • --filter '/::testMethod .*#(5|6|7)$/'

在匹配数据供给器时有一些额外的快捷方式,参见。

例 3.3: 过滤器的快捷方式

  • --filter 'testMethod#2'

  • --filter 'testMethod#2-4'

  • --filter '#2'

  • --filter '#2-4'

  • --filter 'testMethod@my named data'

  • --filter 'testMethod@my.*data'

  • --filter '@my named data'

  • --filter '@my.*data'

--testsuite

只运行名称与给定模式匹配的测试套件。

--group

只运行来自指定分组(可以多个)的测试。可以用 @group标注为测试标记其所属的分组。

@author标注是 @group的一个别名,允许按作者来筛选测试。

--exclude-group

排除来自指定分组(可以多个)的测试。可以用 @group标注为测试标记其所属的分组。

--list-groups

列出所有有效的测试分组。

--test-suffix

只查找文件名以指定后缀(可以多个)结尾的测试文件。

--report-useless-tests

更严格对待事实上不测试任何内容的测试。详情参见。

--strict-coverage

更严格对待意外的代码覆盖。详情参见。

--strict-global-state

更严格对待全局状态篡改。详情参见。

--disallow-test-output

更严格对待测试执行期间产生的输出。详情参见。

--disallow-todo-tests

不执行文档注释块中含有 @todo标注的测试。

--enforce-time-limit

テスト規模に基づいて実行時間制限を追加します。詳細を見る。

--process-isolation

各テストは独立した PHP プロセスで実行されます。

--no-globals-backup

$GLOBALS をバックアップおよび復元しません。詳細をご覧ください。

--static-backup

ユーザー定義クラスの静的プロパティをバックアップおよび復元します。詳細をご覧ください。

--colors

カラー出力を使用します。 Windows では、ANSICON または ConEmu を使用します。

このオプションには 3 つの値があります:

  • Never: カラー出力をまったく使用しません。これは、 --colors オプションが使用されない場合のデフォルト値です。

  • auto: 現在の端末がカラーをサポートしていない場合、出力が他のコマンドにパイプされている場合、または出力がファイルにリダイレクトされている場合、カラー出力は使用されず、他の場合にはカラーが使用されます。

  • always: 現在の端末がカラーをサポートしていない場合、出力が別のコマンドにパイプされる場合、または出力がファイルにリダイレクトされる場合でも、常にカラー出力を使用します。

--colors オプションが使用され、値が指定されていない場合、値として auto が選択されます。

--columns

出力に使用される列の数を定義します。その値が max として定義されている場合、現在の端末でサポートされている列の最大数が使用されます。

--stderr

STDOUT ではなく STDERR への出力を選択します

--stop-on-error

最初のエラーの後に実行を停止します。

--stop-on-failure

最初のエラーまたは失敗が発生した後に実行を停止します。

--stop-on-risky

危険なテストに初めて遭遇したときに実行を停止します。

--stop-on-skipped

スキップされたテストが初めて発生したときに実行を停止します。

--stop-on-incomplete

不完全なテストが初めて発生したときに実行を停止します。

--verbose

不完全なテストまたはスキップされたテストの名前など、より詳細な情報を出力します。

--debug

実行開始時のテストの名前などのデバッグ情報を出力します。

--loader

使用する PHPUnit_Runner_TestSuiteLoader 実装を指定します。

標準テスト スイート ローダーは、現在の作業ディレクトリおよび PHP の include_path 構成ディレクティブで指定されたすべてのディレクトリ内でソース ファイルを検索します。 Project_Package_Class などのクラス名は、Project/Package/Class.php という名前のソース ファイルに対応します。

--repeat

指定された回数だけテストを繰り返します。

--タップ

Test Anything Protocol (TAP) を使用してテストの進行状況を報告します。詳細をご覧ください。

--testdox

テストの進行状況をアジャイルドキュメントで報告します。詳細をご覧ください。

--printer

使用する結果出力プリンター(プリンター)を指定します。エクスポーター クラスは、PHPUnit_Util_Printer を拡張し、PHPUnit_Framework_TestListener インターフェイスを実装する必要があります。

--bootstrap

テストする前に「ブートストラップ」PHP ファイルを実行します。

--configuration, -c

XML ファイルから構成情報を読み取ります。詳細をご覧ください。

phpunit.xml または phpunit.xml.dist (この順序で) が現在の作業ディレクトリに存在し、 --configuration が使用されていない場合、設定はこのファイルから自動的に読み取られます。

--no-configuration

現在の作業ディレクトリ内の phpunit.xml と phpunit.xml.dist を無視します。

--include-path

指定されたパス (複数可) を PHP の include_path の先頭に追加します。

-d

指定された PHP 構成オプションの値を設定します。

注意

4.8 以降、パラメータの後にオプションを配置できないことに注意してください。

第 4 章 修正点

テスト作成で最も時間のかかる部分の 1 つは、シナリオ全体を既知の状態に設定し、テスト完了後に変更するコードを作成することです。初期状態に復元します。この既知の状態はテスト フィクスチャと呼ばれます。

では、基本コンテキストは非常に単純で、$stack 変数に格納される配列です。ただし、ほとんどの場合、ベースは単純な配列よりもはるかに複雑であり、ベースの作成に使用されるコードの量もそれに応じて増加します。テストの実際の内容は、基本環境の確立によって生じるノイズの中に失われます。この問題は、同様の基本環境を必要とする複数のテストを作成する場合にさらに悪化します。テスト フレームワークの助けがなければ、テストを作成するたびに環境を構築するコードを繰り返す必要があります。

PHPUnit 支持共享建立基境的代码。在运行某个测试方法前,会调用一个名叫 setUp()的模板方法。 setUp()是创建测试所用对象的地方。当测试方法运行结束后,不管是成功还是失败,都会调用另外一个名叫 tearDown()的模板方法。 tearDown()是清理测试所用对象的地方。

在中,我们在测试之间运用生产者-消费者关系来共享基境。这并非总是预期的方式,甚至有时是不可能的。展示了另外一个编写测试 StackTest的方式。在这个方式中,不再重用基境本身,而是重用建立基境的代码。首先声明一个实例变量, $stack,用来替代方法内的局部变量。然后把 array基境的建立放到 setUp()方法中。最后,从测试方法中去除冗余代码,在 assertEquals()断言方法中使用新引入的实例变量 $this->stack替代方法内的局部变量 $stack。

例 4.1: 用 setUp() 建立栈的基境

<?phpclass StackTest extends PHPUnit_Framework_TestCase{    protected $stack;    protected function setUp()    {        $this->stack = array();    }    public function testEmpty()    {        $this->assertTrue(empty($this->stack));    }    public function testPush()    {        array_push($this->stack, 'foo');        $this->assertEquals('foo', $this->stack[count($this->stack)-1]);        $this->assertFalse(empty($this->stack));    }    public function testPop()    {        array_push($this->stack, 'foo');        $this->assertEquals('foo', array_pop($this->stack));        $this->assertTrue(empty($this->stack));    }}?>
ログイン後にコピー

测试类的每个测试方法都会运行一次 setUp()和 tearDown()模板方法(同时,每个测试方法都是在一个全新的测试类实例上运行的)。

另外, setUpBeforeClass()与 tearDownAfterClass()模板方法将分别在测试用例类的第一个测试运行之前和测试用例类的最后一个测试运行之后调用。

下面这个例子中展示了测试用例类中所有可用的模板方法。

例 4.2: 展示所有可用模板方法的例子

<?phpclass TemplateMethodsTest extends PHPUnit_Framework_TestCase{    public static function setUpBeforeClass()    {        fwrite(STDOUT, __METHOD__ . "\n");    }    protected function setUp()    {        fwrite(STDOUT, __METHOD__ . "\n");    }    protected function assertPreConditions()    {        fwrite(STDOUT, __METHOD__ . "\n");    }    public function testOne()    {        fwrite(STDOUT, __METHOD__ . "\n");        $this->assertTrue(TRUE);    }    public function testTwo()    {        fwrite(STDOUT, __METHOD__ . "\n");        $this->assertTrue(FALSE);    }    protected function assertPostConditions()    {        fwrite(STDOUT, __METHOD__ . "\n");    }    protected function tearDown()    {        fwrite(STDOUT, __METHOD__ . "\n");    }    public static function tearDownAfterClass()    {        fwrite(STDOUT, __METHOD__ . "\n");    }    protected function onNotSuccessfulTest(Exception $e)    {        fwrite(STDOUT, __METHOD__ . "\n");        throw $e;    }}?>
ログイン後にコピー
<strong>phpunit TemplateMethodsTest</strong>PHPUnit 5.2.0 by Sebastian Bergmann and contributors.TemplateMethodsTest::setUpBeforeClassTemplateMethodsTest::setUpTemplateMethodsTest::assertPreConditionsTemplateMethodsTest::testOneTemplateMethodsTest::assertPostConditionsTemplateMethodsTest::tearDown.TemplateMethodsTest::setUpTemplateMethodsTest::assertPreConditionsTemplateMethodsTest::testTwoTemplateMethodsTest::tearDownTemplateMethodsTest::onNotSuccessfulTestFTemplateMethodsTest::tearDownAfterClassTime: 0 seconds, Memory: 5.25MbThere was 1 failure:1) TemplateMethodsTest::testTwoFailed asserting that <boolean:false> is true./home/sb/TemplateMethodsTest.php:30FAILURES!Tests: 2, Assertions: 2, Failures: 1.
ログイン後にコピー

setUp() 多 tearDown() 少

理论上说, setUp()和 tearDown()是精确对称的,但是实践中并非如此。实际上,只有在 setUp()中分配了诸如文件或套接字之类的外部资源时才需要实现 tearDown()。如果 setUp()中只创建纯 PHP 对象,通常可以略过 tearDown()。不过,如果在 setUp()中创建了大量对象,你可能想要在 tearDown()中 unset()指向这些对象的变量,这样它们就可以被垃圾回收机制回收掉。对测试用例对象的垃圾回收动作则是不可预知的。

如果两个基境建立工作略有不同的测试该怎么办?有两种可能:

  • 如果两个 setUp()代码仅有微小差异,把有差异的代码内容从 setUp()移到测试方法内。

  • 如果两个 setUp()是确实不一样,那么需要另外一个测试用例类。参考基境建立工作的不同之处来命名这个类。

基境共享

有几个好的理由来在测试之间共享基境,但是大部分情况下,在测试之间共享基境的需求都源于某个未解决的设计问题。

一个有实际意义的多测试间共享基境的例子是数据库连接:只登录数据库一次,然后重用此连接,而不是每个测试都建立一个新的数据库连接。这样能加快测试的运行。

用 setUpBeforeClass()和 tearDownAfterClass()模板方法来分别在测试用例类的第一个测试之前和最后一个测试之后连接与断开数据库。

例 4.3: 在同一个测试套件内的不同测试之间共享基境

<?phpclass DatabaseTest extends PHPUnit_Framework_TestCase{    protected static $dbh;    public static function setUpBeforeClass()    {        self::$dbh = new PDO('sqlite::memory:');    }    public static function tearDownAfterClass()    {        self::$dbh = NULL;    }}?>
ログイン後にコピー

需要反复强调的是:在测试之间共享基境会降低测试的价值。潜在的设计问题是对象之间并非松散耦合。如果解决掉潜在的设计问题并使用桩件(stub)(参见)来编写测试,就能达成更好的结果,而不是在测试之间产生运行时依赖并错过改进设计的机会。

全局状态

使用单件(singleton)的代码很难测试。使用全局变量的代码也一样。通常情况下,欲测代码和全局变量之间会强烈耦合,并且其创建无法控制。另外一个问题是,一个测试对全局变量的改变可能会破坏另外一个测试。

在 PHP 中,全局变量是这样运作的:

  • 全局变量 $foo = 'bar';实际上是存储为 $GLOBALS['foo'] = 'bar';的。

  • $GLOBALS这个变量是一种被称为 超全局变量的变量。

  • 超全局变量是一种在任何变量作用域中都总是可用的内建变量。

  • 在函数或者方法的变量作用域中,要访问全局变量 $foo,可以直接访问 $GLOBALS['foo'],或者用 global $foo;来创建一个引用全局变量的局部变量。

除了全局变量,类的静态属性也是一种全局状态。

默认情况下,PHPUnit 用一种更改全局变量与超全局变量( $GLOBALS、 $_ENV、 $_POST、 $_GET、 $_COOKIE、 $_SERVER、 $_FILES、 $_REQUEST)不会影响到其他测试的方式来运行所有测试。同时,还可以选择将这种隔离扩展到类的静态属性。

注意

对全局变量和类的静态属性的备份与还原操作使用了 serialize()与 unserialize()。

某些类的实例对象(比如 PDO)无法序列化,因此如果把这样一个对象存放在比如说 $GLOBALS数组内时,备份操作就会出问题。

在中所讨论的 @backupGlobals标注可以用来控制对全局变量的备份与还原操作。另外,还可以提供一个全局变量的黑名单,黑名单中的全局变量将被排除于备份与还原操作之外,就像这样:

class MyTest extends PHPUnit_Framework_TestCase{    protected $backupGlobalsBlacklist = array('globalVariable');    // ...}
ログイン後にコピー

注意

在方法(例如 setUp()方法)内对 $backupGlobalsBlacklist属性进行设置是无效的。

在“@backupStaticAttributes”一节中提到的 @backupStaticAttributes标注可以用于在每个测试之前备份所有已声明类的静态属性值并在其后恢复。

它所处理的并不只是测试类自身,而是在测试开始时已声明的所有类。它只作用于静态类属性,不作用于函数内声明的静态变量。

注意

只有启用了 @backupStaticAttributes的测试方法才会在方法之前执行此操作。如果在此之前运行的某个没有启用 @backupStaticAttributes的测试方法改变了静态属性的值,那么被备份及还原的将会是这个改变后的值——而非初始声明时提供的默认值。PHP 并不额外记录任何静态变量的声明时提供的初始默认值。

同样的情况也发生于测试内部新加载/声明的类的静态属性上。它们也无法在测试结束之后复原为声明时提供的原始默认值,因为无从得知这些默认值。这些被修改过的值会泄漏到后继测试中。

对单元测试而言,推荐在 setUp()中显式的重置测试中使用到的静态属性(最好同时在 tearDown()中执行重置,这样就保证不会影响到后继的测试)。

可以提供黑名单来将静态属性从备份与还原操作中排除出去:

class MyTest extends PHPUnit_Framework_TestCase{    prot
ログイン後にコピー
関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート