单元测试的那点事儿

      作为一个开发人员,必需对自己的代码负责,而一个行之有效的方法就是编写单元测试,虽然说现在的变化很快,可能今天订好的接口,明天就会因为新需求的介入而面临重构的问题,当然这个问题只要软件在发展都是不可避免的事情,所以导致了单元测试的成本升高,而造成很多滞后问题,这就会造成:虽然一直在提倡测试先行,测试驱动的开发模式不能成为长久的行之有效的方案。但是单元测试,果然很重要。

      从Java开发转向PHP(虽然java还没有机会进入公司实践),正是因为PHP的快速开发的特点,很少有机会提及说要记得写单元测试这种话。所以这块就感觉的更加的匮乏,当然也有可能跟我目前的公司开发模式有关系。

     好了话不多说了,进入正题。

     PHP中的单元测试,有一个类库PHPUnit现在最新版本应该有3.5了(我的测试环境:PHP的版本要求5.2.5,pear的版本要求1.9.1,phpunit:3.4.15),PHPUnit的安装是依赖pear安装的,安装的方式我就不说了,我相信在网上可以找到很多行之有效的方法。

     安装好之后(为了方便我配置了环境),命令行下就可以通过输入 phpunit来查看一些提供的命令和版本信息了。

1

      其中的几个命令选项如:coverage-开头的是因为我在本机上安装了XDEBUG库出现的,我不的不说xdebug确实是一个很好很棒的工具,能分析代码的性能,也能测试代码覆盖率。xdebug的安装我也不赘述了,google出同样有很多,我之前也有写了一些。

      单元测试说简单也简单,主要涉及到两个类,分别是单元测试类,和单元测试的套件类:(以下实例调试环境为Zend studio 8.0),在使用单元测试之前,你需要将你的单元测试类库包含到你的工程中,右击工程->build IncludePath

2 3

  1. 单元测试类PHPUnit_Framework_TestCase

创建一个class,继承PHPUnit_Framework_TestCase,

 1: class MyTest extends PHPUnit_Framework_TestCase {
 2:     
 3:     protected function setUp() {
 4:         parent::setUp();
 5:     }
 6:  
 7:     protected function tearDown() {
 8:         parent::tearDown();
 9:     }
 10: }

setUp和tearDown分别是每个单元测试方法执行的前置和后置操作。

单元测试的测试方法,都是以test开头进行测试:如:

 1: public function testGetName() {
 2:     $this->assertTrue('myname' == 'myname');
 3: }
 4:  
 5: public function testGetEmail() {
 6:     $this->assertEquals('myname', 'myname1');
 7: }

      分别写两个测试方法:执行会发现:

4

       当正确的时候,和在该方法的前面显示一个绿色的钩钩,而如果是失败的方法,则会显示一个红叉叉,而上面的那条状态颜色就会是红色的。

      而如果我这个单元测试类中所有方法都测试通过的时候,则会显示绿色的通行颜色:

5

单元测试就是这么简单的,对于他里面有啥测试方法,可以查看PHPUnit/frameWork下的testCase类,可以看到很多验证的方法提供,我上面用到的只是其中的两个而已。

   2.  单元测试套件PHPUnit_Framework_TestSuite

      套件,名其曰就是多个测试的集合,可以同时测试多个测试类。

      测试套件有两种写法,两种写法都可以在命令行下运行:

     1)以继承的方式:

      将上面的单元测试做一个套件:

 1: require_once 'PHPUnit\Framework\TestSuite.php';
 2: require_once 'MyTest.php';
 3: class MyTestSuite extends PHPUnit_Framework_TestSuite {
 4:  
 5:     public function __construct() {
 6:         $this->setName('MyTestSuite');
 7:         $this->addTestSuite('MyTest');
 8:     }
 9:  
 10:     public static function suite() {
 11:         return new self();
 12:     }
 13: }

      执行:

6

这种方式也是zend studio创建的时候默认提供的,testSuite中提供的方法就是你的套件的方法了,这个方式通常比较简单。但是对于testSuite中需要很多注意的。这个也可以从他的源代码中看出来,

      2)以调用的方式

       还是将上面的测试类做测试:

 1: require_once('MyTest.php');
 2: class MyTestSuite2 {
 3:     static public function main() {
 4:        PHPUnit_TextUI_TestRunner::run(self::suite());
 5:     }
 6:     
 7:     static public function suite() {
 8:         $suite = new PHPUnit_Framework_TestSuite('MyTestSuite');
 9:         $suite->addTestSuite('MyTest');
 10:         return $suite;
 11:     }
 12: }

     这种方式在Zend Studio中运行会有问题(我本地测试是这样),但是用命令行运行是可以的:

7

       说明:命令行下运行:

               .  代表测试方法通过

               I  代表该方法没有实现

               F 代表方法测试失败

               E 代表方法测试有错误输出

通常往一个套件中添加测试方法是通过addTestSuite和addTest两个来完成:实现代码如下:

 1: /**
 2:  * Adds the tests from the given class to the suite.
 3:  *
 4:  * @param mixed $testClass
 5:  * @throws InvalidArgumentException
 6:  */
 7:     public function addTestSuite($testClass)
 8:     {
 9:         if (is_string($testClass) && class_exists($testClass)) {
 10:             $testClass = new ReflectionClass($testClass);
 11:         }
 12:  
 13:         if (!is_object($testClass)) {
 14:             throw PHPUnit_Util_InvalidArgumentHelper::factory(
 15:               1, 'class name or object'
 16:             );
 17:         }
 18:  
 19:         if ($testClass instanceof PHPUnit_Framework_TestSuite) {
 20:             $this->addTest($testClass);
 21:         }
 22:  
 23:         else if ($testClass instanceof ReflectionClass) {
 24:             $suiteMethod = FALSE;
 25:  
 26:             if (!$testClass->isAbstract()) {
 27:                 if ($testClass->hasMethod(PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME)) {
 28:                     $method = $testClass->getMethod(
 29:                       PHPUnit_Runner_BaseTestRunner::SUITE_METHODNAME
 30:                     );
 31:  
 32:                     if ($method->isStatic()) {
 33:                         $this->addTest(
 34:                           $method->invoke(NULL, $testClass->getName())
 35:                         );
 36:  
 37:                         $suiteMethod = TRUE;
 38:                     }
 39:                 }
 40:             }
 41:  
 42:             if (!$suiteMethod && !$testClass->isAbstract()) {
 43:                 $this->addTest(new PHPUnit_Framework_TestSuite($testClass));
 44:             }
 45:         }
 46:  
 47:         else {
 48:             throw new InvalidArgumentException;
 49:         }
 50:     }

通过addTestSuite添加的,应该是一个testSuite或是testCase或是一个类中含有名为suite的静态方法方可。

我们再来看看addTest:

 1: /**
 2:  * Adds a test to the suite.
 3:  *
 4:  * @param PHPUnit_Framework_Test $test
 5:  * @param array $groups
 6:  */
 7:     public function addTest(PHPUnit_Framework_Test $test, $groups = array())
 8:     {
 9:         $class = new ReflectionClass($test);
 10:  
 11:         if (!$class->isAbstract()) {
 12:             $this->tests[]  = $test;
 13:             $this->numTests = -1;
 14:  
 15:             if ($test instanceof PHPUnit_Framework_TestSuite &&
 16:                 empty($groups)) {
 17:                 $groups = $test->getGroups();
 18:             }
 19:  
 20:             if (empty($groups)) {
 21:                 $groups = array('__nogroup__');
 22:             }
 23:  
 24:             foreach ($groups as $group) {
 25:                 if (!isset($this->groups[$group])) {
 26:                     $this->groups[$group] = array($test);
 27:                 } else {
 28:                     $this->groups[$group][] = $test;
 29:                 }
 30:             }
 31:         }
 32:     }

这个方法就比较简单了,就是从testSuite中拿到注册的test方法。

而对于testSuite的构造方法,对齐传入的参数的规定也是比较复杂:

 1: /**
 2:  * Constructs a new TestSuite:
 3:  *
 4:  * - PHPUnit_Framework_TestSuite() constructs an empty TestSuite.
 5:  *
 6:  * - PHPUnit_Framework_TestSuite(ReflectionClass) constructs a
 7:  * TestSuite from the given class.
 8:  *
 9:  * - PHPUnit_Framework_TestSuite(ReflectionClass, String)
 10:  * constructs a TestSuite from the given class with the given
 11:  * name.
 12:  *
 13:  * - PHPUnit_Framework_TestSuite(String) either constructs a
 14:  * TestSuite from the given class (if the passed string is the
 15:  * name of an existing class) or constructs an empty TestSuite
 16:  * with the given name.
 17:  *
 18:  * @param mixed $theClass
 19:  * @param string $name
 20:  * @throws InvalidArgumentException
 21:  */
 22:     public function __construct($theClass = '', $name = '')
 23:     {
 24:         $argumentsValid = FALSE;
 25:  
 26:         if (is_object($theClass) &&
 27:             $theClass instanceof ReflectionClass) {
 28:             $argumentsValid = TRUE;
 29:         }
 30:  
 31:         else if (is_string($theClass) && $theClass !== ''
 32:                  && class_exists($theClass, FALSE)) {
 33:             $argumentsValid = TRUE;
 34:  
 35:             if ($name == '') {
 36:                 $name = $theClass;
 37:             }
 38:  
 39:             $theClass = new ReflectionClass($theClass);
 40:         }
 41:  
 42:         else if (is_string($theClass)) {
 43:             $this->setName($theClass);
 44:             return;
 45:         }
 46:  
 47:         if (!$argumentsValid) {
 48:             throw new InvalidArgumentException;
 49:         }
 50:  
 51:         $filename = $theClass->getFilename();
 52:  
 53:         if (strpos($filename, 'eval()') === FALSE) {
 54:             PHPUnit_Util_Filter::addFileToFilter(realpath($filename), 'TESTS');
 55:         }
 56:  
 57:         if ($name != '') {
 58:             $this->setName($name);
 59:         } else {
 60:             $this->setName($theClass->getName());
 61:         }
 62:  
 63:         $constructor = $theClass->getConstructor();
 64:  
 65:         if ($constructor !== NULL &&
 66:             !$constructor->isPublic()) {
 67:             $this->addTest(
 68:               self::warning(
 69:                 sprintf(
 70:                   'Class "%s" has no public constructor.',
 71:  
 72:                   $theClass->getName()
 73:                 )
 74:               )
 75:             );
 76:  
 77:             return;
 78:         }
 79:  
 80:         $names = array();
 81:  
 82:         foreach ($theClass->getMethods() as $method) {
 83:             if (strpos($method->getDeclaringClass()->getName(), 'PHPUnit_') !== 0) {
 84:                 $this->addTestMethod($theClass, $method, $names);
 85:             }
 86:         }
 87:  
 88:         if (empty($this->tests)) {
 89:             $this->addTest(
 90:               self::warning(
 91:                 sprintf(
 92:                   'No tests found in class "%s".',
 93:  
 94:                   $theClass->getName()
 95:                 )
 96:               )
 97:             );
 98:         }
 99:     }

我的第二种方法也是通过这中构造函数的方式来实现。

好了··就这样··有啥疑问或是纰漏的地方欢迎交流

speech.gif posted on 2011-03-16 21:21 肖虾米 阅读( ...) 评论( ...) 编辑 收藏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值