PHP单元测试利器:PHPUnit深入理解
创始人
2024-07-21 05:51:41
0

在本系列文章的前两篇中初探PHP单元测试利器:PHPUnit和PHP单元测试利器:PHPUnit深入用法中,分别介绍了PHPUnit的基本用法和PHPUnit中的一些重要用法。在本文中,笔者将为大家介绍PHPUnit中的两个高级概念和用法,尽管它不一定在你的日常单元测试中都用到,但理解和学会它们的用法对学习PHPUnit还是十分重要的。

PHPUnit中的Annotations

如果有其他编程语言经验的开发者,应该对Annotations(注解)不陌生,其实在PHPUnit中,一个简单的如下面的一段注释也可以认为是Annotations:

  1. class MyTestClass extends PHPUnit_Framework_TestCase  
  2. {  
  3. /**  
  4. * Testing the answer to “do you love unit tests?”  
  5. */ 
  6. public function testDoYouLoveUnitTests()  
  7. {  
  8. $love = true;  
  9. $this->assertTrue($love);  
  10. }  
  11. }  
  12. ?> 

可以看到,其实一段以/** **/为标记的文字,就可以认为是一种Annotations,但Annotations其实不单单是简单的注释,它是与一个程序元素相关联信息或者元数据的标注,它不影响程序的运行,但相关的软件工具或框架能够将其转换成特殊的元数据标记,以方便开发者以更少的代码去提高效率(比如通过。如果你熟悉Java,则会发现在Java SE 5中及象Spring等框架中,都大量使用了Annotations。

然而,由于PHP并不象Java那样是编译性语言,因此本身缺乏去解析Annotations的机制,但幸好PHPUnit去提供了这样的功能,我们以下面的代码为例:

  1. class MyMathClass  
  2. {  
  3. /**  
  4. * Add two given values together and return sum  
  5. */ 
  6. public function addValues($a,$b)  
  7. {  
  8. return $a+$b;  
  9. }  
  10. }  
  11. ?> 

上面的只是一个简单的加法的例子,为此,我们使用Annotations去编写一个单元测试,在上两篇文章中,我们采用的是手工编写单元测试的方法,而本文中,将介绍使用PHPUnit命令行的方法,自动生成单元测试的框架,方法如下:

首先把上面的类保存为MyMathClass.PHP,然后在命令行下运行如下命令:

  1. phpunit –skeleton-test MyMathClass 

这时PHPUnit会自动生成如下的框架单元测试代码:

  1. require_once '/path/to/MyMathClass.php';  
  2. /**  
  3. * Test class for MyMathClass.  
  4. * Generated by PHPUnit on 2011-02-07 at 12:22:07.  
  5. */ 
  6. class MyMathClassTest extends PHPUnit_Framework_TestCase  
  7. {  
  8. /**  
  9. * @var MyMathClass  
  10. */ 
  11. protected $object;  
  12. /**  
  13. * Sets up the fixture, for example, opens a network connection.  
  14. * This method is called before a test is executed.  
  15. */ 
  16. protected function setUp()  
  17. {  
  18. $this->object = new MyMathClass;  
  19. }  
  20. /**  
  21. * Tears down the fixture, for example, closes a network connection.  
  22. * This method is called after a test is executed.  
  23. */ 
  24. protected function tearDown()  
  25. {  
  26. }  
  27. /**  
  28. * @todo Implement testAddValues().  
  29. */ 
  30. public function testAddValues()  
  31. {  
  32. // Remove the following lines when you implement this test.  
  33. $this->markTestIncomplete(  
  34. 'This test has not been implemented yet.' 
  35. );  
  36. }  
  37. }  
  38. ?> 

可以看到,PHPUnit为我们生成的单元测试代码自动引入了原来的MyMathClass.PHP,同时也生成了setUp和tearDown方法,但唯一的核心单元测试代码是留给了我们编写。如果想在这个基础上更快速的生成我们想要的单元测试代码,要如何实现呢?没错,就是使用annotations!我们可以在原来的MyMathClass.PHP中加入如下的annotations。

  1. class MyMathClass  
  2. {  
  3. /**  
  4. * Add two given values together and return sum  
  5. * @assert (1,2) == 3  
  6. */ 
  7. public function addValues($a,$b)  
  8. {  
  9. return $a+$b;  
  10. }  
  11. }  
  12. ?>  

然后再象上述一样在命令行运行:

  1. phpunit –skeleton-test MyMathClass 

这个时候会为我们生成如下的单元测试代码:

  1. /**  
  2. * Generated from @assert (1,2) == 3.  
  3. */ 
  4. public function testAddValues()  
  5. {  
  6. $this->assertEquals(  
  7. 3,  
  8. $this->object->addValues(1,2)  
  9. );  
  10. }  
  11. ?> 

看到了么?我们在原有的类中加入了注解@assert(1,2)==3,则PHPUnit自动为我们生成了正确的单元测试代码。当然,可以参考PHPUnit手册,学习到更多的关于@assert注解使用的规则。

下面再举一个例子来讲解annotations。假设我们的程序中的一个方法,只是仅需要数据的输入,并且不依赖XML或者数据库提供数据源,则为了测试这个方法,我们可能想到的一个方法是在程序中设置一个测试数据集去测试,但这里介绍一个比较简单的方法,就是使用注解@dataProvider,修改MyMathClass.PHP如下:

  1. /**  
  2. * Data provider for test methods below  
  3. */ 
  4. public static function provider()  
  5. {  
  6. return array(  
  7. array(1,2,3),  
  8. array(4,2,6),  
  9. array(1,5,7)  
  10. );  
  11. }  
  12. /**  
  13. * Testing addValues returns sum of two values  
  14. * @dataProvider provider  
  15. */ 
  16. public function testAddValues($a,$b,$sum)  
  17. {  
  18. $this->assertEquals(  
  19. $sum,  
  20. $this->object->addValues($a,$b)  
  21. );  
  22. }  
  23. ?> 

可以看到,这里使用了注解@dataProvider,指明了测试用例的数据提供者是由provider方法返回的一个数组。所以在单元测试时,数组中的第0个元素则会赋值给$a,第1个元素则会赋值给b,第3个元素则会赋值给sum,可以看到,上面的第3个数组提供的数据是不能通过单元测试的,因为1+5不等于7。

此外,这里还简单介绍两个常用的annotations,比如@expectedException注解可以测试代码中是否正确抛出了异常,比如:

  1. class ExceptionTest extends PHPUnit_Framework_TestCase{      
  2. /**    
  3.    * @expectedException InvalidArgumentException     */      
  4. public function testException()    {    
  5.   }  
  6. }  
  7.  
  8. ?>  

这里就用注解的方法表示testException中必须抛出的异常类型为InvalidArgumentException。

另外一个是@cover注解。它的作用是标识PHPUnit只为类中的哪些方法或作用域生成测试代码,比如:

  1. /**  
  2.      * @covers SampleClass::publicMethod  
  3.      * @covers SampleClass::  
  4.      * @covers HelperClass  
  5.      */ 
  6.     public function testMethod()  
  7.     {  
  8.         $result = SampleClass::method();  

则PHPUnit只为SampleClass类中的publicMethod方法、SampleClass类中的所有非public声明的方法和HelperClass类或者它的其中一个父类产生单元测试代码。

#p#

PHPUnit中的Mocking

在介绍Mocking前,先来看下为什么要使用Mocking。举一个数据库查询的例子,比如在某个应用中,如果要测试一个数据库的应用,但假如这个数据库的测试要耗费很多资源以及编写很复杂的单元测试的代码的话,可以尝试使用Mocking技术。举例说明如下:

  1. class Database  
  2. {  
  3. public function reallyLongTime()  
  4. {  
  5. $results = array(  
  6. array(1,'test','foo value')  
  7. );  
  8. sleep(100);  
  9. return $results;  
  10. }  
  11. }  
  12. ?> 

在上面这个例子中,我们模拟了一个数据库的操作,认为它需要运行很长时间。接下来我们编写其单元测试代码如下:

  1. require_once '/path/to/Database.php';  
  2. class DatabaseTest extends PHPUnit_Framework_TestCase  
  3. {  
  4. private $db = null;  
  5. public function setUp()  
  6. {  
  7. $this->db = new Database();  
  8. }  
  9. public function tearDown()  
  10. {  
  11. unset($this->db);  
  12. }  
  13. /**  
  14. * Test that the "really long query" always returns values  
  15. */ 
  16. public function testReallyLongReturn()  
  17. {  
  18. $mock = $this->getMock('Database');  
  19. $result = array(  
  20. array(1,'foo','bar test')  
  21. );  
  22. $mock->expects($this->any())  
  23. ->method('reallyLongTime')  
  24. ->will($this->returnValue($result));  
  25. $return = $mock->reallyLongTime();  
  26. $this->assertGreaterThan(0,count($return));  
  27. }  
  28. }  
  29. ?>  

注意看这段代码中有趣的地方,这里,使用了PHPUnit中的getMock对象方法,这里实际上是模拟生成一个Database类的“伪实例”了,这里生成了$mock这个mock对象实例,以方便接着的单元测试中用到。接下来的这三行代码:

  1.  $mock->expects($this->any())  
  2. ->method('reallyLongTime')  
  3. ->will($this->returnValue($result)); 

它们的含义为:无论方法reallyLongtime执行了多长时间,始终最后会直接返回$result这个数组的结果。这样,你就可以通过mocking技术很轻易地去实现在单元测试中,绕过某些复杂的逻辑部分,而节省大量的宝贵时间提高测试效率。

下面的这个例子,讲解的是Mocking技术中的更高级用法Mockbuilder。依然以上面的例子说明:

  1. public function testReallyLongRunBuilder()  
  2. {  
  3. $stub = $this->getMockBuilder('Database')  
  4. ->setMethods(array(  
  5. 'reallyLongTime' 
  6. ))  
  7. ->disableAutoload()  
  8. ->disableOriginalConstructor()  
  9. ->getMock();  
  10. $result = array(array(1,'foo','bar test'));  
  11. $stub->expects($this->any())  
  12. ->method('reallyLongTime')  
  13. ->will($this->returnValue($result));  
  14. $this->assertGreaterThan(0,count($return));  
  15. }  
  16. ?> 

通过使用Mockbuilder,我们可以不用通过构造函数的方法去初始化一个mock对象。这段代码跟上一段代码的功能其实是一样的,只不过留意一下新的两个方法: disableAutoload和disableOriginalConstructor,其功能分别是禁止使用PHP的内置的autoload初始构造方法和禁止调用该类原有的构造函数。最后再看一个例子:

  1. /**  
  2. * Testing enforcing the type to "array" like the "enforceTypes"  
  3. * method does via type hinting  
  4. */ 
  5. public function ttestReallyLongRunBuilderConstraint()  
  6. {  
  7. $stub = $this->getMock('Database',array('reallyLongTime'));  
  8. $stub->expects($this->any())  
  9. ->method('reallyLongTime')  
  10. ->with($this->isType('array'));  
  11. $arr = array('test');  
  12. $this->assertTrue($stub-> reallyLongTime ($arr));  
  13. }  
  14. ?> 

在这里,我们使用了with方法,其中这个方法中指定了要传入的参数类型为array数组类型,最后这个断言是通过了,因为返回的的确是数组类型。
 

【编辑推荐】

  1. PHP单元测试利器:PHPUnit深入用法
  2. 初探PHP单元测试利器:PHPUnit
  3. PHP开发者工资翻倍需做到的5件事
  4. PHP企业级应用之常见缓存技术深入解读
  5. PHP 5.3.6 RC1发布 修复多处BUG(附下载)

相关内容

热门资讯

如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
20个非常棒的扁平设计免费资源 Apple设备的平面图标PSD免费平板UI 平板UI套件24平图标Freen平板UI套件PSD径向平...
德国电信门户网站可实时显示全球... 德国电信周三推出一个门户网站,直观地实时提供其安装在全球各地的传感器网络检测到的网络攻击状况。该网站...
为啥国人偏爱 Mybatis,... 关于 SQL 和 ORM 的争论,永远都不会终止,我也一直在思考这个问题。昨天又跟群里的小伙伴进行...
《非诚勿扰》红人闫凤娇被曝厕所... 【51CTO.com 综合消息360安全专家提醒说,“闫凤娇”、“非诚勿扰”已经被黑客盯上成为了“木...
2012年第四季度互联网状况报... [[71653]]  北京时间4月25日消息,据国外媒体报道,全球知名的云平台公司Akamai Te...