• 认真地记录技术中遇到的坑!

PHPUnit单元测试

PHP 悠悠 2年前 (2017-12-07) 723次浏览 0个评论

单元测试是保证代码程序质量的一种重要手段。对于核心代码以及复杂的算法等都应该编写单元测试。

注意单元测试注重于测试某个功能,一个单元测试方法类不应该有复杂的流程控制语句。如果需要对某一列功能进行测试,则需要编写集成测试。

配置PHPUnit

PHPUnit Github地址为:https://github.com/sebastianbergmann/phpunit

官网地址为:https://phpunit.de/

官网英文文档地址:https://phpunit.de/manual/current/en/index.html

Linux

首先需要开启 phar 支持,一般都会支持,在 php.ini文件中添加下面一行

suhosin.executor.include.whitelist = phar

下载和安装 phpunit

wget https://phar.phpunit.de/phpunit-6.5.phar
chmod +x phpunit-6.5.phar
# 将PHPunit添加到环境变量
sudo mv phpunit-6.5.phar /usr/local/bin/phpunit
# 检查安装是否成功
phpunit --version

# 也可以不用添加环境变量,直接使用
php phpunit-6.5.phar --version

此处下载的为最新版的phar文件,用于PHP7以上,注意查看当前的PHP版本,下载相应的扩展。

PHP7: https://phar.phpunit.de/phpunit-6.5.2.phar

PHP5.6: https://phar.phpunit.de/phpunit-5.7.25.phar

windows

在浏览器中下载相应的 phar 文件:https://phar.phpunit.de/phpunit-6.5.2.phar
把它放到你的PHP安装根目录中,如果你的PHP根目录已经添加环境变量则可以直接使用,否则要添加环境变量。这里建议把下载的 phar 文件重命名为 phpunit.phar,或者创建一个 phpunit.cmd 的文件来启动 phpunit。如下是参考的 cmd 文件内容:

echo @php "%~dp0phpunit.phar" %* > phpunit.cmd
phpunit --version

PHPStorm中使用

在PHPStorm中使用单元测试,需要配置 PHPUnit 的安装环境。

选择菜单 File -> Settings -> Languages & Frameworks -> PHP -> Debug -> PHPUnit。在右边栏中选择 Path to phpunit.phar,配置下载的 phpunit.phar 文件的目录即可。

对于测试文件:继承 PHPUnit_Framework_TestCase 类或者 PHPUnit\Framework\TestCase 类的文件,可以选择右键运行,PHPStorm会把它当做测试文件运行,软件下方会出现测试是否通过的绿条和红条。

为类添加测试的方法是把鼠标移动到要测试的类,方法,文件旁边,右键选择 goto -> Test Object 可以创建测试类。

Composer

对于使用 Composer 管理工具管理的项目,只需要在 composer.json 文件中添加相应的依赖或者使用 composer 依赖命令。

composer require --dev phpunit/phpunit ^6.5

# 下面两项为可选的功能:分别测试时间和数据库交互测试
composer require --dev phpunit/php-invoker
composer require --dev phpunit/dbunit

编写单元测试

PHPUnit的单元测试主要针对编写的类等,在单元测试文件中,所有以 test 开头的方法才会被识别为测试方法。

测试之间的依赖

PHPUnit支持在测试方法之间显示声明依赖。分为两种测试方法:

  • 生产者:被依赖者,在测试方法之后返回数据
  • 消费者:依赖者,依赖使用生成者返回的数据进行测试

通过在标准的 PHPDoc 注释中声明注解 @depends 声明依赖

<?php
use PHPUnit\Framework\TestCase;

class StackTest extends TestCase
{
    /**
     * 生产者,生成一个空数组,并返回这个空数组
     */
    public function testEmpty()
    {
        $stack = [];
        $this->assertEmpty($stack);

        return $stack;
    }

    /**
     * 消费者,依赖生产者 testEmpty 方法返回的数组
     * @depends testEmpty
     * 同时也是消费者:返回操作之后的数组
     */
    public function testPush(array $stack)
    {
        array_push($stack, 'foo');
        $this->assertEquals('foo', $stack[count($stack)-1]);
        $this->assertNotEmpty($stack);

        return $stack;
    }

    /**
     * 消费者,依赖生产者 testPush 方法返回的数组
     * @depends testPush
     */
    public function testPop(array $stack)
    {
        $this->assertEquals('foo', array_pop($stack));
        $this->assertEmpty($stack);
    }
}
?>

在上面的例子中,使用注解 @depends 声明测试方法之间的依赖,这对于依赖数组 $stack 是共享的,也就是说 testPush 和 testPop 中修改共享,如果要使得他们之间独立,使用 @depends clone 代替 @depends。

一个测试方法可以依赖多个生产者测试方法:

<?php
use PHPUnit\Framework\TestCase;

class MultipleDependenciesTest extends 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(
            ['first', 'second'],
            func_get_args()
        );
    }
}
?>

专门的数据提供者(参数化测试)

可以使用注解 @dataProvider 为测试方法指定测试参数。这些测试参数的提供方法会在第一次调用 setUp 方法和 setUpBeforeClass 方法之前执行。

<?php
use PHPUnit\Framework\TestCase;

class DataTest extends TestCase
{
    /**
     * @dataProvider additionProvider
     */
    public function testAdd($a, $b, $expected)
    {
        $this->assertEquals($expected, $a + $b);
    }

    public function additionProvider()
    {
        return [
            [0, 0, 0],
            [0, 1, 1],
            [1, 0, 1],
            [1, 1, 3]
        ];
    }
}
?>

测试结果:

phpunit DataTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.

...F

Time: 0 seconds, Memory: 5.75Mb

There was 1 failure:

1) DataTest::testAdd with data set #3 (1, 1, 3)
Failed asserting that 2 matches expected 3.

/home/sb/DataTest.php:9

FAILURES!
Tests: 4, Assertions: 4, Failures: 1.

在上面的例子中,additionProvider方法返回的二维数组,PHPUnit会自动拆开,遍历最外面一维,分别对应测试。此外数据提供则还可以有下面的提供形式:

// 使用下标
public function additionProvider()
{
    return [
        'adding zeros'  => [0, 0, 0],
        'zero plus one' => [0, 1, 1],
        'one plus zero' => [1, 0, 1],
        'one plus one'  => [1, 1, 3]
    ];
}

// 一个可以读取csv文件数据集的类
class 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++;
    }
}
// 使用外部的数据集
public function additionProvider()
{
    return new CsvFileIterator('data.csv');
}

可以同时使用 @depends 和 @dataProvider


<?php use PHPUnit\Framework\TestCase; class DependencyAndDataProviderComboTest extends TestCase { public function provider() { return [['provider1'], ['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( ['provider1', 'first', 'second'], func_get_args() ); } } ?>

测试结果如下:

phpunit --verbose DependencyAndDataProviderComboTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.

...F

Time: 0 seconds, Memory: 3.50Mb

There 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:31

FAILURES!
Tests: 4, Assertions: 4, Failures: 1.

测试PHP异常

使用注解 @expectedException 来进行异常测试,PHP 的 error_reporting 运行时配置会限制 PHPUnit 是否将某些错误转换为异常。

<?php
use PHPUnit\Framework\TestCase;

class ExpectedErrorTest extends TestCase
{
    /**
     * @expectedException PHPUnit\Framework\Error
     */
    public function testFailingInclude()
    {
        include 'not_existing_file.php';
    }
}
?>

测试结果:

phpunit -d error_reporting=2 ExpectedErrorTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.

.

Time: 0 seconds, Memory: 5.25Mb

OK (1 test, 1 assertion)

上面的测试可以看出,抛出指定的异常,则测试通过。

测试输出

可以测试输出是否符合预期,使用 expectOutputString 方法:

<?php
use PHPUnit\Framework\TestCase;

class OutputTest extends TestCase
{
    public function testExpectFooActualFoo()
    {
        $this->expectOutputString('foo');
        print 'foo';
    }

    public function testExpectBarActualBaz()
    {
        $this->expectOutputString('bar');
        print 'baz';
    }
}
?>

测试结果:

phpunit OutputTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.

.F

Time: 0 seconds, Memory: 5.75Mb

There was 1 failure:

1) OutputTest::testExpectBarActualBaz
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-'bar'
+'baz'


FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

PHPUnit 测试输出的结果会尽量详细,比较测试出现错误的地方:

<?php
use PHPUnit\Framework\TestCase;

class ArrayWeakComparisonTest extends TestCase
{
    public function testEquality() {
        $this->assertEquals(
            [1, 2, 3, 4, 5, 6],
            ['1', 2, 33, 4, 5, 6]
        );
    }
}
?>

测试结果:

phpunit ArrayWeakComparisonTest
PHPUnit 6.5.0 by Sebastian Bergmann and contributors.

F

Time: 0 seconds, Memory: 5.25Mb

There was 1 failure:

1) ArrayWeakComparisonTest::testEquality
Failed 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:7

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

断言

在单元测试中,需要对测试结果进行判断,这会大量运用到断言。PHPUnit提供了功能丰富的断言方法。如下:详细的使用方法可以参考文档:https://phpunit.de/manual/current/en/appendixes.assertions.html

布尔类型

  • assertTrue 断言为真
  • assertFalse 断言为假

NULL类型

  • assertNull 断言为NULL
  • assertNotNull 断言非NULL

数字类型

  • assertEquals 断言等于
  • assertNotEquals 断言不等于
  • assertGreaterThan 断言大于
  • assertGreaterThanOrEqual 断言大于等于
  • assertLessThan 断言小于
  • assertLessThanOrEqual 断言小于等于

字符类型

  • assertEquals 断言等于
  • assertNotEquals 断言不等于
  • assertContains 断言包含
  • assertNotContains 断言不包含
  • assertContainsOnly 断言只包含
  • assertNotContainsOnly 断言不只包含

数组类型

  • assertEquals 断言等于
  • assertNotEquals 断言不等于
  • assertArrayHasKey 断言有键
  • assertArrayNotHasKey 断言没有键
  • assertContains 断言包含
  • assertNotContains 断言不包含
  • assertContainsOnly 断言只包含
  • assertNotContainsOnly 断言不只包含

对象类型

  • assertAttributeContains 断言属性包含
  • assertAttributeContainsOnly 断言属性只包含
  • assertAttributeEquals 断言属性等于
  • assertAttributeGreaterThan 断言属性大于
  • assertAttributeGreaterThanOrEqual 断言属性大于等于
  • assertAttributeLessThan 断言属性小于
  • assertAttributeLessThanOrEqual 断言属性小于等于
  • assertAttributeNotContains 断言不包含
  • assertAttributeNotContainsOnly 断言属性不只包含
  • assertAttributeNotEquals 断言属性不等于
  • assertAttributeNotSame 断言属性不相同
  • assertAttributeSame 断言属性相同
  • assertSame 断言类型和值都相同
  • assertNotSame 断言类型或值不相同
  • assertObjectHasAttribute 断言对象有某属性
  • assertObjectNotHasAttribute 断言对象没有某属性

class类型

  • assertClassHasAttribute 断言类有某属性
  • assertClassHasStaticAttribute 断言类有某静态属性
  • assertClassNotHasAttribute 断言类没有某属性
  • assertClassNotHasStaticAttribute 断言类没有某静态属性

文件相关

  • assertFileEquals 断言文件内容等于
  • assertFileExists 断言文件存在
  • assertFileNotEquals 断言文件内容不等于
  • assertFileNotExists 断言文件不存在

XML相关

  • assertXmlFileEqualsXmlFile 断言XML文件内容相等
  • assertXmlFileNotEqualsXmlFile 断言XML文件内容不相等
  • assertXmlStringEqualsXmlFile 断言XML字符串等于XML文件内容
  • assertXmlStringEqualsXmlString 断言XML字符串相等
  • assertXmlStringNotEqualsXmlFile 断言XML字符串不等于XML文件内容
  • assertXmlStringNotEqualsXmlString 断言XML字符串不相等

转载请注明出处 PHPUnit单元测试
喜欢 (16)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址