单元测试是保证代码程序质量的一种重要手段。对于核心代码以及复杂的算法等都应该编写单元测试。
注意单元测试注重于测试某个功能,一个单元测试方法类不应该有复杂的流程控制语句。如果需要对某一列功能进行测试,则需要编写集成测试。
配置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);
returnstack;
}
/**
* 消费者,依赖生产者 testEmpty 方法返回的数组
* @depends testEmpty
* 同时也是消费者:返回操作之后的数组
*/
public function testPush(array stack)
{
array_push(stack, 'foo');
this->assertEquals('foo',stack[count(stack)-1]);this->assertNotEmpty(stack);
returnstack;
}
/**
* 消费者,依赖生产者 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;
protectedkey = 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() {
returnthis->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字符串不相等