博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
PHPUnit学习03---使用Mock对象解决测试依赖
阅读量:5843 次
发布时间:2019-06-18

本文共 5121 字,大约阅读时间需要 17 分钟。

本文目的

单元测试过程中经常会遇到被测试函数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));
    
}
}
 
?>

执行结果:

上面的方法看似不复杂,只是继承了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));
//真实的调用,并断言调用结果
    
}
}
 
?>

 

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

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

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对象无能为力。

相关链接

  • PHPUnit Mock API官方介绍 
  • PHPUnit Mock API 使用介绍
声明:如有转载本博文章,请注明出处。您的支持是我的动力!文章部分内容来自互联网,本人不负任何法律责任。
本文转自bourneli博客园博客,原文链接:
http://www.cnblogs.com/bourneli/archive/2012/06/29/2570440.html
,如需转载请自行联系原作者
你可能感兴趣的文章
codevs——2822 爱在心中
查看>>
Python基础班---第一部分(基础)---Python基础知识---认识Python
查看>>
JAVA MAC 配置
查看>>
1134 最长上升子序列 (序列型 DP)
查看>>
js冒泡排序
查看>>
第一次作业 4班卢炳武
查看>>
抽象类的调用
查看>>
使用硬盘,安装双系统,Win7+CentOS
查看>>
Javascript学习总结
查看>>
php 用正则替换中文字符一系列问题解决
查看>>
ActiveMQ应用笔记一:基本概念&安装
查看>>
大话数据结构之四(串)
查看>>
加热炉简是新来的整个系统的板
查看>>
Mockito使用注意事项
查看>>
[LeetCode] Palindrome Linked List 回文链表
查看>>
UVA - 825Walking on the Safe Side(dp)
查看>>
评论:人才流失强力折射出现实畸形人才观
查看>>
git服务器gitlab之搭建和使用--灰常好的git服务器【转】
查看>>
基于机器学习的web异常检测——基于HMM的状态序列建模,将原始数据转化为状态机表示,然后求解概率判断异常与否...
查看>>
虚拟运营商10月或大面积放号 哭穷背后仍有赢家
查看>>