PHPUnit单元测试
一、概述
1. 什么是单元测试?
2. 作用是什么?
3. 哪些程序需要写单元测试(PHP)?
二、PHPUnit的安装与集成CI框架
三、PHPUnit的使用
编写测试用例
<?php class 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); } } ?>
<?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) ); } } ?> 也可以是这样: <?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( '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) ); } } ?>
对PHP错误进行测试 默认情况下,PHPUnit 将测试在执行中触发的 PHP 错误、警告、通知都转换为异常。利用这些异常,就可以,比如说,预期测试将触发 PHP 错误 <?php class ExpectedErrorTest extends PHPUnit_Framework_TestCase { /** * @expectedException PHPUnit_Framework_Error */ public function testFailingInclude() { include 'not_existing_file.php'; } } ?> 测试 phpunit -d error_reporting=2 ExpectedErrorTest PHPUnit 5.2.0 by Sebastian Bergmann and contributors. . Time: 0 seconds, Memory: 5.25Mb OK (1 test, 1 assertion)
命令行测试执行器
说明
PHPUnit 测试执行器可通过phpunit 调用,例如在CI中: tongkundeMacBook-Pro:www tongkun$ cd tests/ tongkundeMacBook-Pro:tests tongkun$ phpunit PHPUnit 5.0.0 by Sebastian Bergmann and contributors. ............. 13 / 13 (100%) Time: 195 ms, Memory: 17.50Mb OK (13 tests, 8 assertions)
说明:
先进入测试的根目录,执行phpunit 命令,后面可跟具体的目录或文件,也可不跟,如果没有则会对当前目录的所有文件执行单元测试,对于每个测试的运行,PHPUnit命令行工具会输出一个字符来指示进展:
常用命令行选项
四、基境(fixture)
什么是基境?
“基境”就是编写代码来将整个场景设置成某个已知的状态,并在测试结束后将其复原到初始状态。这个已知的状态称为测试的 基境(fixture)。
基境的建立
PHPUnit 支持共享建立基境的代码。在运行某个测试方法前,会调用一个名叫 setUp() 的模板方法。setUp() 是创建测试所用对象的地方。当测试方法运行结束后,不管是成功还是失败,都会调用另外一个名叫 tearDown() 的模板方法。tearDown() 是清理测试所用对象的地方。
<?php class 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() 模板方法将分别在测试用例类的第一个测试运行之前和测试用例类的最后一个测试运行之后调用。基境共享可以在共享数据库连接时使用;
五、组织测试
用文件系统来编排测试套件
例如:
phpunit controllers/WelcomeTest.php
用 XML 配置来编排测试套件
<?xml version="1.0" encoding="UTF-8"?> <phpunit //配置文件 colors="true" //颜色 stopOnFailure="false" //出错后是否终止 bootstrap="../application/third_party/CIUnit/bootstrap_phpunit.php"> //bootstrap 地址 <php> <server name="SERVER_NAME" value="http://www.nyhdev.com" /> </php> <testsuites> //测试套件 <testsuite name="ControllerTests"> <directory>controllers</directory> //要测试的目录 </testsuite> <testsuite name="HelperTests"> <directory suffix=".php">helpers</directory> </testsuite> <testsuite name="LibTests"> <directory suffix=".php">libs</directory> </testsuite> <testsuite name="ModelTests"> <directory suffix=".php">models</directory> </testsuite> <testsuite name="SystemTests"> <directory suffix=".php">system</directory> </testsuite> </testsuites> <filter> <blacklist> <directory>vendor</directory> <directory>libs</directory> </blacklist> <whitelist> <directory>controllers</directory> <directory>fixtures</directory> <directory>models</directory> <directory>helpers</directory> </whitelist> </filter> </phpunit>
六、有风险的测试
无用测试
PHPUnit 可以更严格对待事实上不测试任何内容的测试。此项检查可以用命令行选项 --report-useless-tests 或在 PHPUnit 的 XML 配置文件中设置 beStrictAboutTestsThatDoNotTestAnything="true" 来启用。
在启用本项检查后,如果某个测试未进行任何断言,它将被标记为有风险。仿件对象中的预期和诸如 @expectedException 这样的标注同样视为断言。
测试执行期间产生的输出
PHPUnit 可以更严格对待测试执行期间产生的输出。 此项检查可以用命令行选项 --disallow-test-output 或在 PHPUnit 的 XML 配置文件中设置 beStrictAboutOutputDuringTests="true" 来启用。
在启用本项检查后,如果某个测试产生了输出,例如,在测试代码或被测代码中调用了 print,它将被标记为有风险。
七、未完成的测试与跳过的测试
未完成的测试
开始写新的测试用例类时,可能想从写下空测试方法开始,比如:
public function testSomething() { }
假如把成功的测试视为绿灯、测试失败视为红灯,那么还额外需要黄灯来将测试标记为未完成或尚未实现。PHPUnit_Framework_IncompleteTest 是一个标记接口,用于将测试方法抛出的异常标记为测试未完成或目前尚未实现而导致的结果。PHPUnit_Framework_IncompleteTestError 是这个接口的标准实现。
例如:我们有一个测试文件,contrllers/WelcomeTest.php,其中有一个测试方法,通过在测试方法中调用markTestIncomplete()将这个测试标记为未完成。
public function testTest() { $this->assertTrue(true,'这里可以正常工作'); $this->markTestIncomplete('此测试尚未实现'); }
在PHPUnit命令行测试执行器中输出,未完成的测试标记为1, 如下:
localhost:tests tongkun$ phpunit PHPUnit 5.0.0 by Sebastian Bergmann and contributors. R..I...RRRR.. 13 / 13 (100%) Time: 187 ms, Memory: 17.75Mb OK, but incomplete, skipped, or risky tests! Tests: 13, Assertions: 8, Incomplete: 1, Risky: 5.
跳过测试
如上,如果有些测试需要某些环境或者配置才能完成,则可选择跳过,通过调用 markTestSkipped() 方法来测试
用 @requires 来跳过测试
除了上述方法,还可以用 @requires 标注来表达测试用例的一些常见前提条件。 事例:
例 7.3: 用 @requires 来跳过测试
<?php /** * @requires extension mysqli */ class DatabaseTest extends PHPUnit_Framework_TestCase { /** * @requires PHP 5.3 */ public function testConnection() { // 测试要求有 mysqli 扩展,并且 PHP >= 5.3 } // ... 所有其他要求有 mysqli 扩展的测试 } ?>
要求安装mysqli苦战和php 5.3 才能执行
#常用断言
前边废话一篇,终于到了关键的断言部分,断言可以说是单元测试的核心,通过断言的校验,保证程序的正确运行,并输出正确的值。