安装PHPUnit

合适的版本

根据官方的Release Announcements

PHPUnit 4.x版本支持PHP 5.4/5.5/5.6
PHPUnit 5.x支持PHP 5.6/7.x
PHPUnit 6.x版本仅支持PHP 7.x。

安装

Composer

利用composer包管理器进行安装是目前较为方便的方式。

composer require phpunit/phpunit ~4.8.0 -vvv

Pear

依据官方说明,PHPUnit不在支持使用pear进行安装。

书写测试代码

测试类应继承PHPUnit_Framework_TestCase(PHPUnit 4.x/5.x)/PHPUnit\Framework\TestCase(PHPUnit 6.x);也可以对这个类进行扩展,扩展的方法类似对异常的扩展。

以下为guzzle/guzzle库中的一个测试:

<?php
namespace GuzzleHttp\Tests\Event;
//use ...;
class ConnectExceptionTest extends \PHPUnit_Framework_TestCase
{
    public function testHasNoResponse()
    {
        //...测试代码
    }
}

简单的测试

测试类一般按照*Test的方式命名,*一般为要测试的类名。

测试方法一般都以test*的方式命名,*一般为要测试的类中的方法名;也可以在方法注释中加入@test注解标记为测试方法。

下列两种书写方法均为测试方法:

<?php
class FooTest extends PHPUnit_Framework_TestCase
{
    public function testMethod1()
    {
        //method1的测试代码
        $this->assertEquals(true, false);
    }
    /**
     * test method2
     * @test
     */
    public function method2()
    {
        //method2的测试代码
        $this->assertEmpty(false);
    }
}

测试代码中调用的assert*方法由PHPUnit_Framework_TestCase提供,方法描述由简单的英文组成,一般都能看懂。

具体的方法可以参照文档

有依赖的测试

这种情况指的是:测试方法需要其他测试方法提供的基境(这里可以解释为参数,即待测试的方法需要其他测试方法提供的结果作为参数)。

需要在方法注释中,以@depends methodName的方式声明需要的基境方法,需要多个基境的按照顺序写下去即可。

例如,method3需要testMethod1method2的运行结果作为参数:

<?php
namespace Foo;

use PHPUnit_Framework_TestCase;

class FooTest extends PHPUnit_Framework_TestCase
{

    public function testMethod1()
    {
        $this->assertEquals(false, false);
        return 0;
    }

    /**
     * 
     * @test
     */
    public function method2()
    {
        $this->assertTrue(true);
        return '';
    }

    /**
     * @test
     * @depends testMethod1
     * @depends method2
     */
    public function method3($a, $b)
    {
        $this->assertEquals($a, $b);
    }

}

有数据供给的测试

这种情况针对的是有较多不同的case,需要不同的测试用例才能测完全的测试。

测试方法中可以传入参数,提供不同参数的方法称为数据提供器方法,在测试中用@dataProvider methodName来进行标记。

后文用个例子来说明这种情况,以下是手册中关于此部分的示例。

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

对异常的测试

有以下三种对异常进行测试的方式:

  • annonation
<?php
class DataTest extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException \Exception
     * @expectedExceptionCode 0
     * @expectedExceptionMessage hello
     * @expectedExceptionMessageRegExp #^he.*#
     */
    public function testCatchExceptionAnnotation()
    {
        throw new \Exception('hello world');
    }
}

@expectedExceptionMessage指定的Message是被抛出异常信息中包含指定的字符,不是完全相同。

  • 调用setException系列方法
<?php
class DataTest extends PHPUnit_Framework_TestCase
{
    public function testCacheExceptionFunction() {
        $this->setExpectedException('\Exception', 'hello', 0);
        $this->setExpectedExceptionRegExp('\Exception', '#he.*#', 0);
        throw new \Exception('hello world');
    }
}

setExpectedException指定的Message是被抛出异常信息中包含指定的字符,不是完全相同。

  • 利用try…catch…特定测试
<?php
class ExceptionTest extends PHPUnit_Framework_TestCase {
    public function testException() {
        try {
            // ... 预期会引发异常的代码 ...
        }

        catch (InvalidArgumentException $expected) {
            return;
        }

        $this->fail('预期的异常未出现。');
    }
}

1、2方法一般只能对一个异常进行测试,3可以对多种多个异常进行测试,视需求而定。

对错误的测试

PHPUnit里面定义了PHP的错误异常类型,基类PHPUnit_Framework_Error,各种PHP错误类PHPUnit_Framework_Error_Deprecated,PHPUnit_Framework_Error_Notice,PHPUnit_Framework_Error_Warning

对输出的测试

与对异常测试相类似,可以在测试方法开始设定要测试输出的字符,方法有expectOutputRegexexpectOutputString

与异常测试不同的是,expectOutputString指定的字符必须与输出字符完全相同方可通过测试。

<?php

namespace App\Foo;

/**
 * Generated by PHPUnit_SkeletonGenerator on 2017-02-13 at 11:35:47.
 */
class FooTest extends \PHPUnit_Framework_TestCase
{

    public function testEchoStr()
    {
        $this->expectOutputString("hello");
        echo "hello";
    }

    public function testEchoStrRegex()
    {
        $this->expectOutputRegex("#^he.*#");
        echo "hello";
    }

}

跳过测试

未完成测试

开始写新的测试用例类时,可能想从写下空测试方法开始,而PHPUnit会将空测试方法标记为成功,这没有任何意义。

所以,我们需要把空测试方法标记为未完成markTestIncomplete,类似annoation@todo

<?php

namespace App\Foo;

/**
 * Generated by PHPUnit_SkeletonGenerator on 2017-02-13 at 11:35:47.
 */
class FooTest extends \PHPUnit_Framework_TestCase
{

    public function testUncompleted()
    {
        $this->markTestIncomplete(
          '此测试目前尚未实现。'
        );
    }

}
环境条件不符合跳过测试

由于不同环境中PHP版本、扩展、依赖等不相同,编写的单元测试可能在不同环境中出现不同测测试结果。

这时我们需要对测试用例进行条件限定,对于PHP版本PHP扩展操作系统函数存在,可以使用@requires进行限定。

<?php

namespace App\Foo;

/**
 * Generated by PHPUnit_SkeletonGenerator on 2017-02-13 at 11:35:47.
 */
class FooTest extends \PHPUnit_Framework_TestCase
{

    /**
     * @requires PHP 5.3
     * @requires OS Linux
     * @requires function mb_substr
     * @requires extension redis
     */
    public function testSkipped()
    {
        // some tests
    }

}

对于复杂的条件,我们可以在测试代码中自行编写条件,进行跳过判定。

<?php
class DatabaseTest extends PHPUnit_Framework_TestCase
{
    protected function setUp()
    {
        if (!extension_loaded('mysqli')) {
            $this->markTestSkipped(
              'The MySQLi 扩展不可用。'
            );
        }
    }

    public function testConnection()
    {
        // ...
    }
}
?>

小结

以上是书写PHPUnit测试的基本格式。