ThinkPHP官网首页

ENGLISH

Blog

使用单元测试工具

发布时间: 2009-01-13 10:46

敏捷开发思想

首先,编写类定义;
其次,编写测试用例;
第三,实现类;
第四,实现测试用例;
第五,反复测试修改。

[separator]

准备工作

安装PHPUnit

自动安装
pear install phpunit

手动安装
下载http://pear.phpunit.de/get/PHPUnit-3.3.9.tgz,解压到PHP目录,

将pear-phpunit和pear-phpunit.bat,复制到PHP目录,重命名为phpunit和phpunit.bat,将PHP目录加入PATH路径。

编辑phpunit,修改为:

PHP代码
  1. #! php -d safe_mode=Off   
  2. set_include_path(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'PHPUnit-3.3.9' . PATH_SEPARATOR . get_include_path());   
  3.   
  4. require_once 'PHPUnit/Util/Filter.php';   
  5.   
  6. PHPUnit_Util_Filter::addFileToFilter(__FILE__'PHPUNIT');   
  7.   
  8. require 'PHPUnit/TextUI/Command.php';   
  9. ?>  


编辑phpunit.bat,假设PHP路径为D:\PHP,修改为:
@echo off
php -d safe_mode=Off D:\PHP\phpunit %*

现在在终端运行phpunit,看看命令参数。

单元测试实践

编写类定义

PHP代码
  1. /**  
  2.  * 计算器类  
  3.  */  
  4. class Calculator   
  5. {   
  6.     /**  
  7.      * 加法运算  
  8.      *  
  9.      * @access public  
  10.      */  
  11.     public function add()   
  12.     {   
  13.     }   
  14.   
  15.     /**  
  16.      * 乘法运算  
  17.      *  
  18.      * @access public  
  19.      */  
  20.     public function multiply()   
  21.     {   
  22.     }   
  23. }   
  24. ?>  

编写测试用例 

PHP代码
  1. require_once 'Calculator.php';   
  2.   
  3. /**  
  4.  * 计算器测试用例  
  5.  */  
  6. class CalculatorTest extends PHPUnit_Framework_TestCase   
  7. {   
  8.     /**  
  9.      * @var    object  
  10.      * @access protected  
  11.      */  
  12.     protected $object;   
  13.   
  14.     /**  
  15.      * 建立测试,在测试方法执行前被调用  
  16.      *  
  17.      * @access protected  
  18.      */  
  19.     protected function setUp()   
  20.     {   
  21.         $this->object = new Calculator();   
  22.     }   
  23.     /**  
  24.      * 拆除测试,在测试方法执行后被调用  
  25.      *  
  26.      * @access protected  
  27.      */  
  28.     protected function tearDown()   
  29.     {   
  30.     }   
  31.   
  32.     /**  
  33.      * 测试加法运算  
  34.      *  
  35.      * @access public  
  36.      */  
  37.     public function testadd()   
  38.     {   
  39.     }   
  40.   
  41.     /**  
  42.      * 测试乘法运算  
  43.      *  
  44.      * @access public  
  45.      */  
  46.     public function testmultiply()   
  47.     {   
  48.     }   
  49. }   
  50. ?>  

实现类 

PHP代码
  1. /**  
  2.  * 计算器类  
  3.  */  
  4. class Calculator   
  5. {   
  6.     /**  
  7.      * 加法运算  
  8.      *  
  9.      * @param  int  $a  
  10.      * @param  int  $b  
  11.      * @access public  
  12.      */  
  13.     public function add($a$b)   
  14.     {   
  15.         return $a + $b;   
  16.     }   
  17.   
  18.     /**  
  19.      * 乘法运算  
  20.      *  
  21.      * @param  int  $a  
  22.      * @param  int  $b  
  23.      * @access public  
  24.      */  
  25.     public function multiply($a$b)   
  26.     {   
  27.         return $a * $b;   
  28.     }   
  29. }   
  30. ?>  

实现测试用例 

PHP代码
  1. require_once 'Calculator.php';   
  2.   
  3. /**  
  4.  * 计算器测试用例  
  5.  */  
  6. class CalculatorTest extends PHPUnit_Framework_TestCase   
  7. {   
  8.     /**  
  9.      * @var    object  
  10.      * @access protected  
  11.      */  
  12.     protected $object;   
  13.   
  14.     /**  
  15.      * 建立测试,在测试方法执行前被调用  
  16.      *  
  17.      * @access protected  
  18.      */  
  19.     protected function setUp()   
  20.     {   
  21.         $this->object = new Calculator();   
  22.     }   
  23.   
  24.     /**  
  25.      * 拆除测试,在测试方法执行后被调用  
  26.      *  
  27.      * @access protected  
  28.      */  
  29.     protected function tearDown()   
  30.     {   
  31.     }   
  32.   
  33.     /**  
  34.      * 测试加法运算  
  35.      *  
  36.      * @access public  
  37.      */  
  38.     public function testadd()   
  39.     {   
  40.         $expected = 2;   
  41.         $result = $this->object->add(1, 1);   
  42.         $this->assertEquals($expected$result);   
  43.     }   
  44.   
  45.     /**  
  46.      * 测试乘法运算  
  47.      *  
  48.      * @access public  
  49.      */  
  50.     public function testmultiply()   
  51.     {   
  52.         $expected = 4;   
  53.         $result = $this->object->multiply(2, 2);   
  54.         $this->assertEquals($expected$result);   
  55.     }   
  56. }   
  57. ?>  

执行测试
phpunit CalculatorTest.php

进阶

一些方法和属性被声明为保护类型,我们在测试无返回值或被保护的方法时,继承就能解决问题。为什么要测试被保护方法?我个人认为由于PHP是一种语法宽松的语言,因而必须进行更严格的测试,要从最底层确保方法的正确性。如果必要的话,也可以测试私有方法。 

PHP代码
  1. /**  
  2.  * 计算器类  
  3.  */  
  4. class Calculator   
  5. {   
  6.     /**  
  7.      * 加法运算  
  8.      *  
  9.      * @param  int  $a  
  10.      * @param  int  $b  
  11.      * @access protected  
  12.      */  
  13.     protected function add($a$b)   
  14.     {   
  15.         return $a + $b;   
  16.     }   
  17.   
  18.     /**  
  19.      * 乘法运算  
  20.      *  
  21.      * @param  int  $a  
  22.      * @param  int  $b  
  23.      * @access protected  
  24.      */  
  25.     protected function multiply($a$b)   
  26.     {   
  27.         return $a * $b;   
  28.     }   
  29. }   
  30. ?>  

使用继承来测试被保护方法。 

PHP代码
  1. require_once 'Calculator.php';   
  2.   
  3. class MyCalculator extends Calculator   
  4. {   
  5. }   
  6.   
  7. /**  
  8.  * 计算器测试用例  
  9.  */  
  10. class CalculatorTest extends PHPUnit_Framework_TestCase   
  11. {   
  12.     /**  
  13.      * @var    object  
  14.      * @access protected  
  15.      */  
  16.     protected $object;   
  17.   
  18.     /**  
  19.      * 建立测试,在测试方法执行前被调用  
  20.      *  
  21.      * @access protected  
  22.      */  
  23.     protected function setUp()   
  24.     {   
  25.         $this->object = new MyCalculator();   
  26.     }   
  27.   
  28.     /**  
  29.      * 拆除测试,在测试方法执行后被调用  
  30.      *  
  31.      * @access protected  
  32.      */  
  33.     protected function tearDown()   
  34.     {   
  35.     }   
  36.   
  37.     /**  
  38.      * 测试加法运算  
  39.      *  
  40.      * @access public  
  41.      */  
  42.     public function testadd()   
  43.     {   
  44.         $expected = 2;   
  45.         $result = $this->object->add(1, 1);   
  46.         $this->assertEquals($expected$result);   
  47.     }   
  48.   
  49.     /**  
  50.      * 测试乘法运算  
  51.      *  
  52.      * @access public  
  53.      */  
  54.     public function testmultiply()   
  55.     {   
  56.         $expected = 4;   
  57.         $result = $this->object->multiply(2, 2);   
  58.         $this->assertEquals($expected$result);   
  59.     }   
  60. }   
  61. ?>  

编写总测试套件

PHP代码
  1. <?   
  2. // 设置错误报告为严格等级   
  3. error_reporting(E_ALL | E_STRICT);   
  4.   
  5. // 测试配置文件   
  6. require 'Configure.php';   
  7.   
  8. // Require PHPUnit   
  9. require_once 'PHPUnit/Util/Filter.php';   
  10.   
  11. PHPUnit_Util_Filter::addFileToFilter(__FILE__);   
  12.   
  13. require_once 'PHPUnit/Framework/TestSuite.php';   
  14. require_once 'PHPUnit/Extensions/PhptTestSuite.php';   
  15.   
  16. // Require test suites   
  17. require_once 'Math/AllTests.php';   
  18.   
  19. class AllTests   
  20. {   
  21.     public static function suite()   
  22.     {   
  23.         $suite = new PHPUnit_Framework_TestSuite('MyProject');   
  24.   
  25.         $suite->addTest(Math_AllTests::suite());   
  26.   
  27.         return $suite;   
  28.     }   
  29. }   
  30. ?>  

编写支测试套件

PHP代码
  1. // Require test suites   
  2. require_once 'Math/CalculatorTest.php';   
  3.   
  4. class Math_AllTests   
  5. {   
  6.     public static function suite()   
  7.     {   
  8.         $suite = new PHPUnit_Framework_TestSuite('Math');   
  9.   
  10.         $suite->addTestSuite('Math_CalculatorTest');   
  11.   
  12.         return $suite;   
  13.     }   
  14. }   
  15. ?>  

注意:测试用例这里采用目录名加下划线再加原来的测试用例名,来避免不同目录的同名类冲突问题。

目录结构
Lib
+- Math
   +- Calculator.php
Tests
+- Math
    +- AllTests.php
    +- CalculatorTest.php
|- AllTests.php
|- Configure.php

执行所有测试
phpunit --verbose AllTests.php

备注:phpunit命令可以直接使用不带.php的名称或目录,例如,
phpunit CalculatorTest
phpunit Math
但是这里建议您在每个目录下都写一个测试套件AllTests.php文件,这么做可以为后期测试节省许多时间。因为如果你的项目中有几十个甚至上百个文件或者目录结构很深,将导致PHP脚本超出内存的错误,而且phpunit每次都需要花很长的时间来生成测试套件。

自动化测试

自动生成测试用例
phpunit --skeleton Calculator.php

该命令会生成一个CalculatorTest.php的测试用例,里面有两个未完成的测试方法testAdd()和testMultiply(),你需要完善它。

PHP代码
  1. require_once 'PHPUnit/Framework.php';   
  2.   
  3. require_once 'Calculator.php';   
  4.   
  5. /**  
  6.  * Test class for Calculator.  
  7.  * Generated by PHPUnit on 2009-01-13 at 09:30:46.  
  8.  */  
  9. class CalculatorTest extends PHPUnit_Framework_TestCase   
  10. {   
  11.     /**  
  12.      * @var    Calculator  
  13.      * @access protected  
  14.      */  
  15.     protected $object;   
  16.   
  17.     /**  
  18.      * Sets up the fixture, for example, opens a network connection.  
  19.      * This method is called before a test is executed.  
  20.      *  
  21.      * @access protected  
  22.      */  
  23.     protected function setUp()   
  24.     {   
  25.         $this->object = new Calculator;   
  26.     }   
  27.   
  28.     /**  
  29.      * Tears down the fixture, for example, closes a network connection.  
  30.      * This method is called after a test is executed.  
  31.      *  
  32.      * @access protected  
  33.      */  
  34.     protected function tearDown()   
  35.     {   
  36.     }   
  37.   
  38.     /**  
  39.      * @todo Implement testAdd().  
  40.      */  
  41.     public function testAdd() {   
  42.         // Remove the following lines when you implement this test.   
  43.         $this->markTestIncomplete(   
  44.           'This test has not been implemented yet.'  
  45.         );   
  46.     }   
  47.   
  48.     /**  
  49.      * @todo Implement testMultiply().  
  50.      */  
  51.     public function testMultiply() {   
  52.         // Remove the following lines when you implement this test.   
  53.         $this->markTestIncomplete(   
  54.           'This test has not been implemented yet.'  
  55.         );   
  56.     }   
  57. }   
  58. ?>  

持续集成

如果你需要与其他项目集成或集成到IDE工具中,使用ant构建是个不错的注意。
编写ant构建文件,让构建、测试、报告、清理一部到位。你可以将下面内容加入你的build.xml文件中。

XML/HTML代码
  1. xml version="1.0" encoding="utf-8"?>  
  2. <project name="MyProject" default="test">  
  3.   <property name="docs" location="Docs"/>  
  4.   <property name="report" location="Docs/Report"/>  
  5.   
  6.   <target name="prepare">  
  7.     <mkdir dir="${docs}"/>  
  8.     <mkdir dir="${report}"/>  
  9.   target>  
  10.   
  11.   <target name="test" depends="prepare">  
  12.     <exec dir="${basedir}" executable="phpunit" failonerror="true">  
  13.       <arg line="--log-tap ${docs}/log.txt --log-xml ${docs}/log.xml --testdox-html ${docs}/test.html --testdox-text ${docs}/test.txt AllTests.php"/>  
  14.     exec>  
  15.   target>  
  16.   
  17.   <target name="report" depends="prepare">  
  18.     <exec dir="${basedir}" executable="phpunit" failonerror="true">  
  19.       <arg line="--coverage-html ${report} AllTests.php"/>  
  20.     exec>  
  21.   target>  
  22.   
  23.   <target name="clean">  
  24.     <delete dir="${docs}"/>  
  25.   target>  
  26. project>  

Windows下请将其中的两处 executable="phpunit" 改为 executable="phpunit.bat"。
使用以下命令执行任务:
ant test      执行测试
ant report    生成测试报告
ant clean     清理测试目录

最佳实践

每次编写好类定义后,自动生成测试用例。实现类和测试用例。修改,执行测试,反复重复这个过程。每次修改后,马上修改测试用例,以免之后忘记。

最新动态