PHPUnit 基本用法+实例详解
上一篇文章介绍了 PHPUnit 在 windows  的安装和配置。今天我们来介绍一下 PHPUnit 如何编写一些基本的测试用例。我也是在最近才开始慢慢使用的 PHPUnit , 用不足之处,欢迎指正。
编写规范
- 测试类一般以***Test 命名;
- 该类必须继承 PHPUnit_Framework_TestCase 类;
- 类里的测试用例方法一般以test开头,当然也可以通过@test注释来定义一个名字不为test开头的方法为测试方法;
- 测试方法中需要使用断言方法来断言实际传入的参数与期望的参数是否一致来达到测试的目的;
测试用例
- 基本的demo
定义一个类 DemoTest 并保存到 DemoTest.php 文件中
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | <?php
 
 
 
 class DemoTest extends PHPUnit_Framework_TestCase {
 
 
 
 
 public function testPushAndPop() {
 $stack = array ();
 
 $this->assertEquals(0, count($stack));
 array_push($stack, 'foo');
 
 
 $this->assertEquals(1, count($stack));
 
 $this->assertEquals('foo', $stack[count($stack)-1]);
 
 $this->assertEquals('foo', array_pop($stack));
 
 $this->assertEquals(0, count($stack));
 }
 
 
 
 
 
 public function indexEquals() {
 $stack = array (0,1,2,3,4);
 
 $this->assertEquals(2, $stack[0]);
 }
 }
 
 | 
上面的代码中定义了两种测试用例的方法,一种是开头为test, 一种是定义@test标签;两种都可以。
然后运行测试这个类;打开命令窗口,进入该代码保存的文件目录输入:phpunit DemoTest.php
运行结果为:
| 12
 3
 4
 5
 6
 7
 
 | Time:825 ms, Memory: 8.25MbThere was 1 failure:
 1) DemoTest::indexEquals
 Failed asserting that 0 matches expected 2.
 D:\server\apache\htdocs\my_php_code\phpunit\stack\DemoTest.php:32
 FAILURES!
 Tests: 2, Assertions: 6, Failures: 1.
 
 | 
解释一下:最上面的是一些耗时,内存消耗的多少。往下看测试结果说有一个错误,也就是测试未通过,在文件的32行。32行的意思是断言这个数组中索引为0的值为2,显然不是,这里我故意写错,所以测试失败。如果改为0则会显示OK;最后是显示2个测试用例,6个断言,其中一个失败。
- 方法依赖关系
在测试类中,测试用例方法可以有依赖关系。通过依赖关系可以传一些参数到方法中;因为默认的测试用例方法是不能有参数的。
定义一个FunTest 类保存到文件中
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 
 | <?php
 
 
 
 class FuncTest extends PHPUnit_Framework_TestCase {
 
 public function testEmpty() {
 $stack = array ();
 $this->assertEmpty($stack);
 return $stack;
 }
 
 
 
 
 
 
 
 public function testPush(array $stack) {
 array_push($stack, 'foo');
 $this->assertEquals('foo', $stack[count($stack)-1]);
 
 return $stack;
 }
 
 
 
 
 
 public function testPop(array $stack) {
 $this->assertEquals('foo', array_pop($stack));
 $this->assertEmpty($stack);
 }
 }
 
 | 
标签@depends是表示依赖关系,上面的代码中testPop()依赖testPush(),testPush()依赖testEmpty(), 所以当testEmpty()测试通过后返回的变量可以作为testPush(array $stack)的参数。同理,testPop()也是一样。
运行结果如下:
| 12
 
 | Time: 726 ms, Memory: 8.25MbOK (3 tests, 4 assertions)
 
 | 
测试通过
- 测试非依赖关系的方法传入参数
如果非依赖关系的方法,默认是不能有参数的,这个时候怎么样才能传参,PHPUnit 提供给了一个标签,@dataProvider
定义一个DataTest类保存到文件中
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 | <?php
 
 
 
 
 class DataTest extends PHPUnit_Framework_TestCase {
 
 
 
 
 
 public function testAdd($a, $b, $c) {
 
 $this->assertEquals($c, $a + $b);
 }
 
 
 
 
 
 
 
 
 
 public function add_provider() {
 return array (
 array (0, 0, 0),
 array (0, 1, 1),
 array (1, 0, 1),
 array (1, 1, 2),
 );
 }
 }
 
 | 
看上面代码应该就能明白。直接运行phpunit DataTest.php
运行结果如下:
| 12
 
 | Time: 379 ms, Memory: 8.25MbOK (3 tests, 4 assertions)
 
 | 
- 数据提供者方法和依赖关系的限制
这个听起来有点绕口,意思是如果方法依赖和方法提供者同时使用的话,是有限制的。说半天我估计还是一塌糊涂,不解释,直接看代码
定义一个 Data2Test 类保存到文件
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 
 | <?php
 
 
 
 
 
 
 
 class Data2Test extends PHPUnit_Framework_TestCase {
 
 public function testA() {
 return 78;
 }
 
 
 
 
 
 public function testB($a, $b, $c) {
 $this->assertEquals($c, $a + $b);
 return $a;
 }
 
 
 
 
 public function testC($a) {
 var_dump($a);
 }
 
 public function add_provider() {
 
 return array (
 array (0, 0, 0),
 array (0, 1, 1),
 array (1, 0, 1),
 array (1, 1, 2),
 );
 }
 }
 
 | 
解释一下:
testB 依赖于 testA (testA 使用了dataProvider 提供数据)
如果 add_provider 提供的数据至少有一次是成功的,则在成功一次后运行 testC
如果 add_provider 提供的数据没有一次是成功的,则 testC 一次也不会执行
但是 testC 执行的结果永远是 null, 因为 $a 是通过 dataProvider 提供的。不能传入依赖它的测试方法中
好像还是不太明白,反正我是尽力了。慢慢理解吧。
- 通过构造迭代器来为方法提供数据
通过标签 @dataProvider 来直接返回数据作为数据提供者,PHPUnit 也可以通过返回构造器对象来提供数据。上代码
新建 IteratorTest.php 文件 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 
 | <?php
 
 
 
 class myIterator implements Iterator {
 
 private $_position = 0;
 
 private $_array = array (
 array (0, 0, 0),
 array (0, 1, 1),
 array (1, 0, 1),
 array (1, 1, 2)
 );
 
 public function current() {
 echo 0;
 return $this->_array[$this->_position];
 }
 
 public function key() {
 echo 1;
 return $this->_position;
 }
 
 public function next() {
 echo 2;
 ++$this->_position;
 }
 
 public function valid() {
 echo 3;
 return isset($this->_array[$this->_position]);
 }
 
 public function rewind() {
 echo 4;
 return $this->_position = 0;
 }
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | 
 
 class IteratorTest extends PHPUnit_Framework_TestCase {
 
 
 
 
 public function testAdd($a, $b, $c) {
 
 $this->assertEquals($c, $a + $b);
 }
 
 public function add_provider() {
 
 return new myIterator();
 }
 }
 
 | 
先定义了一个构造器,然后返回这个构造器对象,同样也能提供数据。运行 phpunit IteratorTest.php
运行结果如下:
| 12
 
 | Time: 408 ms, Memory: 8.25MbOK (4 tests, 4 assertions)
 
 | 
- 异常测试
有时候我们希望能够抛出我们所期待的异常。这里有三种方法来测试异常,上代码
定义 ThrowTest 类保存到文件
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 
 | <?php
 
 
 
 
 class ThrowTest extends PHPUnit_Framework_TestCase {
 
 
 
 
 
 public function testException1() {
 
 }
 
 
 
 
 public function testException2() {
 $this->setExpectedException('My_Exception');
 }
 
 
 
 
 public function testException3() {
 try {
 
 } catch (My_Exception $e) {
 
 return ;
 }
 $this->fail('一个期望的异常没有被捕获');
 }
 }
 
 | 
代码应该很明白了,不用解释了。
- 错误测试
有时候代码会发生错误,比如某个php文件找不到,文件不可读,php 文件加载失败等。这个时候我们也能进行测试,是否发生错误;PHPUnit 会把错误直接转化为异常PHPUnit_Framework_Error并抛出;我们要做到的是捕获这个异常。上代码。
定义 ErrorTest 类保存到文件中
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | <?php
 
 
 
 class ErrorTest extends PHPUnit_Framework_TestCase {
 
 
 
 
 
 public function testError() {
 
 include '../test.php';
 }
 }
 
 | 
测试显示OK,则证明已经捕获。
- 对输出进行测试
有时候我们需要对程序的指定输出进行测试。比如echo 还是 print() 指定的值是否正确。
定义类 OutputTest 类保存到文件
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | <?php
 
 
 
 class OutputTest extends PHPUnit_Framework_TestCase {
 
 public function testExpectFooActualFoo() {
 $this->expectOutputString('foo');
 print 'foo';
 }
 
 public function testExpectBarActualBaz() {
 $this->expectOutputString('bar');
 print 'baz';
 }
 }
 
 | 
注意: 在严格模式下,本身产生输出的测试将会失败。
- 基镜(fixture)
PHPUnit 支持共享建立基境的代码。在运行某个测试方法前,会调用一个名叫 setUp() 的模板方法。setUp() 是创建测试所用对象的地方。当测试方法运行结束后,不管是成功还是失败,都会调用另外一个名叫 tearDown() 的模板方法。tearDown() 是清理测试所用对象的地方。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | <?phpclass FixTureTest 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));
 }
 }
 
 | 
以上是PHPUnit 官方的一个代码示例。
测试类的每个测试方法都会运行一次 setUp() 和 tearDown() 模板方法(同时,每个测试方法都是在一个全新的测试类实例上运行的)。
另外,setUpBeforeClass() 与 tearDownAfterClass() 模板方法将分别在测试用例类的第一个测试运行之前和测试用例类的最后一个测试运行之后调用。
setUp() 多 tearDown() 少
理论上说,setUp() 和 tearDown() 是精确对称的,但是实践中并非如此。实际上,只有在 setUp() 中分配了诸如文件或套接字之类的外部资源时才需要实现 tearDown() 。如果 setUp() 中只创建纯 PHP 对象,通常可以略过 tearDown()。不过,如果在 setUp() 中创建了大量对象,你可能想要在 tearDown() 中 unset() 指向这些对象的变量,这样它们就可以被垃圾回收机制回收掉。对测试用例对象的垃圾回收动作则是不可预知的。 —PHPUnit 官方网站
总结
好了,今天大概就先介绍这些,已经可以大概写一些测试用例了。当然还有更高级的测试使用方法。现在为什么不讲呢,因为我也不会。。