PHPUnit学习03---使用Mock对象解决测试依赖

简介:

本文目的

单元测试过程中经常会遇到被测试函数A依赖另一个函数B,但是B已经完全测试过,没有必要在测试A的时候重复测试B。如何去除这种不必要的测试呢?本文探讨了如何手动解决测试依赖,更进一步地,结合PHPUnit的Mock API,提出更加优雅,高效的解决方案。

一个例子

假设有一个订单管理类OrderManager,它的私有变量中,有一个OrderDao,当插入订单时,首先OrderManager会检查内参数是否合法,然后调用OrderDao的insert方法,将Order对象插入到数据库中。现在,假设已经测底的对OrderDao的所有方法进行了单元测试,需要测试OrderManager相关方。此时,就产生了测试依赖的问题。具体代码如下:

1
2
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
<?php
class  OrderDao{
     public  function insert($aOrder){   
         if ($aOrder[ 'id' ] == 'order_id_already_existing' ){
             return  -1;
         }
         
         // 这个方法只是简单的模拟,不作实质的数据库操作
         print "connect to db\n" ;
         print "execute query\n" ;
         print "1 row effected\n" ;
         print "insert order {$aOrder['id']} successfully\n" ;
         return  0;
     }
}
 
class  OrderManager{
     private  $_oOrderDao;
     public  function __construct(OrderDao $oOd){
         $ this ->_oOrderDao = $oOd;
     }
     public  function insertOrder($aOrder){
         if (array_key_exists( 'id' , $aOrder) && $aOrder[ 'id' ] != '' ){
             print "order {$aOrder['id']} is valide!\n" ;
             if ($ this ->_oOrderDao->insert($aOrder) == 0){
                 print "call dao insert successfully\n" ;
                 return  true ;
             }
             else {
                 print "insert error!\n" ;
                 return  false ;
             }          
         } else {
             print "order {$aOrder['id']} is invalide!\n" ;
             return  false ;
         }
     }
}
 
?>

假设上面的文件中,OrderDao已经被测试测试,现在需要测试OrderManager::insertOrder方法。这个方法调用了OrderDao::insert方法。下面,先看看手动创建一个mock(mock的中文意识是“模仿”)类进行单元测试的方案。

手动创建Mock

创建一个新的类,称为OrderDaoMock,继承类OrderDao,方法实现时采用一些简单,方便,无意义的实现,如下(ut_order_demo_manual.php):

1
2
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
<?php
require_once 'order_demo.php' ;
 
class  OrderDaoMock extends OrderDao{
     public  function insert($aOrder){
         print "Mock Info: insert order {$aOrder['id']} successfully\n" ;
         return  0;
     }
}
 
class  OrderDemo_TestCase extends PHPUnit_Framework_TestCase{
     public  function testNullIdOrder(){
         $oOm = new  OrderManager( new  OrderDaoMock());
         $aOrder = array( 'id' => '' );
         $ this ->assertFalse($oOm->insertOrder($aOrder));
     }
     
     public  function testNoIdOrder(){
         $oOm = new  OrderManager( new  OrderDaoMock());
         $aOrder = array();
         $ this ->assertFalse($oOm->insertOrder($aOrder));
     }
     
     public  function testSuccessInsertOrder(){
         $oOm = new  OrderManager( new  OrderDaoMock());
         $aOrder = array( 'id' => 'bourneli123456789' );
         $ this ->assertTrue($oOm->insertOrder($aOrder));
     }
}
 
?>

执行结果:

clip_image002

上面的方法看似不复杂,只是继承了OrderDao类,实现了一个哑方法insert。但是,设想如果在真实的系统中,被测试的方法会涉及到许多其他对象,如果每个对象都手动创建一个mock类,工作量还是十分大的。还好,PHPUnit提供了Mock类的API,可以方便的创建这些mock类。

PHPUnit的Mock API

现在,我们看看使用PHPUint的Mock API的版本(ut_order_demo_mock.php):

1
2
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
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
require_once 'order_demo.php' ;
 
class  OrderDemo_TestCase extends PHPUnit_Framework_TestCase
{
     public  function testNullIdOrder(){
             //自动创建一个集成OrderDao的mock对象
         $oMockOrderDao = $ this ->getMock( 'OrderDao' );
         //期望不要调用这个对象的insert方法,如果调用,就会报错
             $oMockOrderDao->expects($ this ->never())->method( 'insert' );
         
         $oOm = new  OrderManager($oMockOrderDao);
         $aOrder = array( 'id' => '' );
         $ this ->assertFalse($oOm->insertOrder($aOrder));
     }
     
     public  function testNoIdOrder(){
         $oMockOrderDao = $ this ->getMock( 'OrderDao' );
         $oMockOrderDao->expects($ this ->never())->method( 'insert' );
     
         $oOm = new  OrderManager($oMockOrderDao);
 
         $aOrder = array();
         $ this ->assertFalse($oOm->insertOrder($aOrder));
     }
     
     public  function testSuccessInsertOrder(){
         $aOrder = array( 'id' => 'bourneli123456789' );
         
         $oMockOrderDao = $ this ->getMock( 'OrderDao' );
         $oMockOrderDao->expects($ this ->once())
                       ->method( 'insert' );
     
         $oOm = new  OrderManager($oMockOrderDao);
         $ this ->assertTrue($oOm->insertOrder($aOrder));
     }
     
     public  function testOrderIdExisting(){
         $aOrder = array( 'id' => 'order_id_already_existing dfd' );
         //自动创建一个集成OrderDao的mock对象
         $oMockOrderDao = $ this ->getMock( 'OrderDao' );
         //期望调用这个对象insert方法,次数任意。在调用时,输入必须是$aOrder对象,
         //返回必须是0。如果不满足这种期望,将会报错。
         $oMockOrderDao->expects($ this ->any())
                       ->method( 'insert' )
                       ->with($aOrder)
                       ->will($ this ->returnValue(0));
     
         $oOm = new  OrderManager($oMockOrderDao);
         $ this ->assertTrue($oOm->insertOrder($aOrder)); //真实的调用,并断言调用结果
     }
}
 
?>

 

上面的例子的执行结果如下:

clip_image004

我们来分析一下上面的代码,

1
$oMockOrderDao = $ this ->getMock( 'OrderDao' );

上面这段代码就为我们完成了手动创建OrderDaoMock类的工作。

1
2
$oMockOrderDao->expects($ this ->any())->method( 'insert' )
         ->with($aOrder)->will($ this ->returnValue(0));

上面这段代码代码为我们完成了四个针对mock对象调用的断言:

1)调用insert方法;

2)调用任意次;

3)调用时,输入参数必须是$aOrder对象;

4)调用结束后,返回参数必须是0.

Mock API给了我们很大的自由度,可以随意操作mock对象的行为,使得用mock进行单元测试十分便捷。根据上面的例子亲自动手实践,你会很容易理解mock对象的原理和作用。

Mock对象

为什么需要mock对象呢?有时候,很难测试被测系统(System Under Test,“被测系统”以下简称SUT),因为SUT依赖一些不能在测试环境使用的组件。这些组件有可能不可用(如第三方系统),或者他们不能返回测试中期望的结果,或者是这些组件执行后会带来负面效果(如修改数据库中的数据)。这时候,就需要mock对象来解决这些问题。Mock对象提供相同的API,供SUT调用,使得SUT可以正常运转。如果希望在测试中大范围的使用mock对象,对程序开发而言也有要求,程序开发过程中必须依照高内聚,底耦合的策略,并且尽量使用接口编程,这样mock类才可以去模仿——通过继承和多态,否则mock对象没有用武之地。这一点,也暴露出了mock类的短板,mock只能模拟类中的public,protected函数,如果是static, private或final函数,mock对象无能为力。

相关链接

声明:如有转载本博文章,请注明出处。您的支持是我的动力!文章部分内容来自互联网,本人不负任何法律责任。

本文转自bourneli博客园博客,原文链接: http://www.cnblogs.com/bourneli/archive/2012/06/29/2570440.html ,如需转载请自行联系原作者
相关文章
|
4月前
|
架构师 测试技术 网络性能优化
dpdk课程学习之练习笔记七(vpp环境搭建及plugin demo测试)
dpdk课程学习之练习笔记七(vpp环境搭建及plugin demo测试)
159 0
|
4月前
|
存储 缓存 网络协议
dpdk课程学习之练习笔记二(arp, udp协议api测试)
dpdk课程学习之练习笔记二(arp, udp协议api测试)
63 0
|
24天前
|
监控 安全 Shell
深入探究App压力测试的关键要点:从零开始学习Monkey
Monkey是Google的自动化测试工具,用于模拟用户随机事件以测试应用的稳定性和压力。它可以在模拟器或设备上运行,通过随机点击发现潜在问题。
24 1
|
29天前
|
敏捷开发 供应链 测试技术
深入理解与应用软件测试中的Mock技术
【2月更文挑战第30天】 在现代软件开发过程中,单元测试是保证代码质量的重要手段。然而,对于高度依赖外部系统或服务的应用来说,传统的单元测试方法往往难以实施。Mock技术应运而生,它通过模拟外部依赖的响应,使开发者可以在隔离的环境中测试目标代码。本文将探讨Mock技术的概念、应用场景以及如何在软件测试中有效地使用Mock对象,以增强测试的灵活性和可靠性。
|
2月前
|
存储 测试技术 Python
带有参数依赖的接口该如何测试?
带有参数依赖的接口该如何测试?
|
2月前
|
JSON 监控 测试技术
依赖第三方的接口如何测试?
依赖第三方的接口如何测试?
|
4月前
|
缓存 监控 网络协议
dpdk课程学习之练习笔记五(kni理解及测试)
dpdk课程学习之练习笔记五(kni理解及测试)
71 0
|
4月前
|
Ubuntu 应用服务中间件 测试技术
dpdk预备学习环境准备之多队列网卡认识及测试
dpdk预备学习环境准备之多队列网卡认识及测试
84 1
|
4月前
|
NoSQL Java API
SpringBoot【ElasticSearch集成 02】Java HTTP Rest client for ElasticSearch Jest 客户端集成(依赖+配置+增删改查测试源码)推荐使用
SpringBoot【ElasticSearch集成 02】Java HTTP Rest client for ElasticSearch Jest 客户端集成(依赖+配置+增删改查测试源码)推荐使用
56 0
|
4月前
|
Java API
SpringBoot【集成ElasticSearch 01】2种方式的高级客户端 RestHighLevelClient 使用(依赖+配置+客户端API测试源码)
SpringBoot【集成ElasticSearch 01】2种方式的高级客户端 RestHighLevelClient 使用(依赖+配置+客户端API测试源码)
75 0