Mock的基本概念和方法
来源:互联网 发布:php oa办公系统源码 编辑:程序博客网 时间:2024/04/24 10:49
本博客(http://blog.csdn.net/livelylittlefish)贴出作者(三二一@小鱼)相关研究、学习内容所做的笔记,欢迎广大朋友指正!
Content
0. 序言
1. 本文议题
2. 应该做什么?
3. 如何做?
3.1 方案一
(1) 建立模拟文件
(2)修改业务逻辑中的调用
(3)修改make文件
(4) 讨论
3.2 方案二
(1) 建立模拟文件
(2) 基本思想
(3)修改make文件
(4) 讨论
(5) 该方案的变种
4. 小结
0.序言
在软件开发中,我们不可避免的要调用一些外部或者系统级别的接口,然而,我们在测试时,也许这些接口或环境并不存在。比如在对我们自己的模块做单元测试时,发现自己的模块依赖的别的模块或接口还没有建立好,如何测试?
Mock概念应运而生,最开始在Java领域,后来各种语言或开发领域均引入该概念。
Mock实际上就是一种模拟和控制外部或者系统级别对象或接口的方法。因此,我们在做测试时,尤其是单元测试或覆盖测试时,不必与真实环境交互即可完成对自己的模块业务逻辑的测试,或许自己的模块需要依赖外部环境。
因此,我们可以总结
Mock的本质是:模拟(mock)你的(代码),来测我的(代码)。
在这里,别人的(代码),或者与硬件相关的(代码),或者暂时未完成的(代码),统称为你的(代码)。
关于单元测试,各种软件工程书籍,http://en.wikipedia.org/wiki/Unit_testing,及其链接有较详细的解释。
关于Mock对象,可参考《测试驱动开发-Test-Driven Development》第7章笔记,http://www.mockobjects.com,http://en.wikipedia.org/wiki/Mock_object,等有较详细的解释。
1.本文议题
在本文中,笔者将以文件操作为例,讲述基本的mock概念和方法。本例中,你的代码your_file.h/.c如下。
/*
* your_file.h
*/
#ifndef _YOUR_FILE_H_
#define _YOUR_FILE_H_
#include
FILE* your_file_open(char *fname);
void your_file_close(FILE* fp);
#endif
Your_file.c是你的代码本来应该有的功能,如打开和关闭文件。
/*
* your_file.c
*/
#include "your_file.h"
FILE* your_file_open(char *fname)
{
FILE *fp = NULL;
fp = fopen(fname, "r");
if (fp == NULL)
{
printf("Fail to open file!/n");
return0;
}
printf("Succeed!/n");
return fp;
}
void your_file_close(FILE* fp)
{
fclose(fp);
}
首先,做如下假设:
(1)由于某种原因,这个.c文件(your_file.c)还有问题;
(2)或者,这个.c文件(your_file.c)还没有完成;
(3)除此以外,我的业务逻辑代码(my_business.c)依赖your_file.c/.h;
(4)而且,此时我们需要对my_business.c做单元测试,例如,要测试其中的某几个函数等;
那么,在这种情况下,我们应该如何测试自己的业务逻辑?即如何测试my_business.c文件?——这将是本文的主要内容。
my_business.c业务逻辑如下。为了方便,将main()放在该文件中,实际应用中,main()应该在main.c或者别的启动文件中。
/*
* my bussiness
*/
#include "your_file.h"
#include
int read_file()
{
FILE* fp = your_file_open("data.txt");
//assert(fp != 0);
/*
* here my business start, for example, read data from the file.
* for test, only print the fp.
*/
printf("%s, %d: file handle = 0x%x/n", __FUNCTION__, __LINE__, (unsigned int)fp);
your_file_close(fp);
return 0;
}
int main(/* int argc, char **argv */)
{
read_file();
return 0;
}
注:本文实验对Win32平台和Linux平台均适用。对于make文件,Linux平台为makefile,win32平台为make.bat。
如果代码所在目录下有data.txt文件,其运行结果如下。
# ./my_bussiness
Succeed!
read_file, 16: file handle = 0x8ba2008
2.应该做什么?
在假设的情况下,现在的问题是,your_file.c可能还没有完成或者其他原因不能使用,而且还要对my_business.c中的read_file()进行测试,且my_business.c依赖your_file.c,怎么办?
由Mock基本概念可知,我们要做的就是模拟(mock)your_file.c文件的功能。
3.如何做?
3.1方案一
方法:模拟your_file.h/.c文件,并修改其中的函数名
(1)建立模拟文件
模拟your_file.h/.c文件,将其功能的简单实现放在mock_your_file.h/.c文件中,如下。
/*
* mock_your_file.h
*/
#ifndef _MOCK_YOUR_FILE_H_
#define _MOCK_YOUR_FILE_H_
#include
FILE* mock_your_file_open(char *fname);
void mock_your_file_close(FILE* fp);
#endif
可以看到,我们将该函数名改了。
/*
* mock_your_file.c
*/
#include "mock_your_file.h"
FILE* mock_your_file_open(char *fname)
{
printf("Succeed!/n");
return (FILE*)0x94f9008;
}
void mock_your_file_close(FILE* fp)
{
fp = 0;
}
可以看到,在打开文件函数中,我们只是返回一个硬编码的指针。现在,mock_your_file.c是一个单独的模块,用来模拟your_file.c的功能。
(2)修改业务逻辑中的调用
函数名改了,我们需要修改my_business.c中的调用。如下。
/*
* my bussiness
*/
#include"mock_your_file.h" //该包含文件也要修改
void read_file()
{
FILE* fp = mock_your_file_open("data.txt"); //新的函数名
//assert(fp != 0);
/*
* here my business start, for example, read data from the file.
* for test, only print the fp.
*/
printf("%s, %d: file handle = 0x%x/n", __FUNCTION__, __LINE__, (unsigned int)fp);
mock_your_file_close(fp); //新的函数名
}
int main(/* int argc, char **argv */)
{
read_file();
return 0;
}
(3)修改make文件
当然,还需要修改makefile文件。如下。
CXX = gcc
CXXFLAGS += -g -Wall -Wextra
TESTS = my_bussiness
all : $(TESTS)
clean :
rm -f $(TESTS) *.o
my_bussiness.o: my_bussiness.c
$(CXX) $(CXXFLAGS) -c $^
#your_file.o: your_file.c
mock_your_file.o: mock_your_file.c
$(CXX) $(CXXFLAGS) -c $^
$(TESTS): my_bussiness.o mock_your_file.o
$(CXX) $(CXXFLAGS) $^ -o $@
可以看到,以前的your_file.c就不再使用了,换成mock文件。
如果是Win32平台,其make.bat文件也要修改。如下。
@echo off
echo start to compile all examples
echo.
cl /wd 4530 /nologo my_bussiness.c mock_your_file.c
echo.
del *.obj
echo done. bye.
pause
至此,就达到了模拟your_file.h/.c的目的。
(4)讨论
实际上,这是一个较为笨重的方法,因为该方案需要修改的东西太多,比如要修改文件名,函数名,还要修改my_business.c文件中的调用,以及make文件,比较麻烦。
一个稍微简单点且不需要修改这么多内容的方法:在模拟文件mock_your_file.h/.c文件中不修改函数名,那么my_business.c就不需要改动,只修改make文件即可。
你甚至可以通过目录隔离的方式,不修改模拟文件名、函数名、make文件等,唯一要做的仅仅是修改模拟的函数的内容即可。但这导致代码可能有两套,维护也麻烦。
等等,这些方法都是比较肤浅的方法,属于体力活,但实现简单。那么,有没有稍微好一些的方法呢?
3.2方案二
方法:使用编译预处理的宏定义,让将fopen函数换个指向,即实际上模拟your_open_file()调用的fopen函数。
(1)建立模拟文件
研究your_open_file()函数的代码发现,其调用的fopen()函数返回的FILE*实际上作为your_open_file()函数的返回值返回。那么,能不能模拟fopen()函数呢?——Of course!
该方案重新编写的mock文件如下。
/*
* mock_your_file.h
*/
#ifndef _MOCK_YOUR_FILE_H_
#define _MOCK_YOUR_FILE_H_
#include
FILE* mock_fopen(const char *fname, const char* option);
void mock_fclose(FILE* fp);
#endif
/*
* mock_your_file.c
*/
#include "mock_your_file.h"
FILE* mock_fopen(const char *fname,const char* option)
{
return (FILE*)0x94f9008;
}
void mock_fclose(FILE* fp)
{
fp = 0;
}
让mock_fopen()函数模拟fopen(),直接返回FILE*。
其他的文件不需要修改,且your_file.h/.c文件仍然使用(区别于方案一),但要修改make文件。
(2)基本思想
该方案的基本思想是:使用编译预处理的宏定义功能进行(符号)常量定义,即将fopen看作一个符号常量,定义该常量的值为模拟函数mock_fopen;因为mock_your_file.c也会被编译,因此,链接时your_file.c中对fopen的调用便转为对mock_your_file.c中的mock_fopen的调用。
What a good idea!
(3)修改make文件
该方案的make文件,Linux平台如下。
CXX = gcc
CXXFLAGS += -g -Wall -Wextra
TESTS = my_business
MOCK_FLAG =-Dfopen=mock_fopen-Dfclose=mock_fclose #定义符号常量
all : $(TESTS)
clean :
rm -f $(TESTS) *.o
your_file.o: your_file.c
$(CXX) $(CXXFLAGS)$(MOCK_FLAG) -c $^ #编译your_file.c时使用该常量
mock_your_file.o: mock_your_file.c
$(CXX) $(CXXFLAGS) -c $^
my_business.o: my_business.c
$(CXX) $(CXXFLAGS) -c $^
$(TESTS): your_file.o mock_your_file.o my_business.o
$(CXX) $(CXXFLAGS) $^ -o $@
可以看出,3个源文件均参与编译、链接,区别于方案一(your_file.c不再参与)。
win32平台的make.bat文件为,
@echo off
echo start to compile all examples
echo.
cl /wd 4996 /nologo /Dfopen=mock_fopen/Dfclose=mock_fclosemy_business.c mock_your_file.c your_file.c
echo.
del *.obj
echo done. bye.
pause
(4)讨论
该方案比较于方案一提出的各种“肤浅”方案,要巧妙的多。其关键之处就是利用编译器的编译预处理的宏定义功能,定义符号常量。将fopen看作符号常量,其值定义为mock_fopen,链接时对符号的resolve处理,即会将对fopen的调用转为对mock_fopen的调用。
从your_file.c编译后的.o文件的符号表中也能看出端倪。
# objdump -t your_file.o
your_file.o: file format elf32-i386
SYMBOL TABLE:
00000000 l df *ABS* 00000000 your_file.c
00000000 l d .text 00000000 .text
00000000 l d .data 00000000 .data
00000000 l d .bss 00000000 .bss
00000000 l d .debug_abbrev 00000000 .debug_abbrev
00000000 l d .debug_info 00000000 .debug_info
00000000 l d .debug_line 00000000 .debug_line
00000000 l d .rodata 00000000 .rodata
00000000 l d .debug_frame 00000000 .debug_frame
00000000 l d .debug_loc 00000000 .debug_loc
00000000 l d .debug_pubnames 00000000 .debug_pubnames
00000000 l d .debug_aranges 00000000 .debug_aranges
00000000 l d .debug_str 00000000 .debug_str
00000000 l d .note.GNU-stack 00000000 .note.GNU-stack
00000000 l d .comment 00000000 .comment
00000000 g F .text 00000055 your_file_open
00000000 *UND* 00000000 mock_fopen
00000000 *UND* 00000000 puts
00000055 g F .text 00000013 your_file_close
00000000 *UND* 00000000 mock_fclose
如果没有MOCK_FLAG,其编译后的符号表和上述符号表的唯一差别就是这两个符号,分别为fopen和fclose。
(5)该方案的变种
将该符号定义放在Your_file.c文件中,即-D命令行方式的另一种方式。
/*
* your_file.c
*/
#include "your_file.h"
#include "mock_your_file.h"
#define fopen mock_fopen
#define fclose mock_fclose
FILE* your_file_open(char *fname)
{
FILE *fp = NULL;
fp = fopen(fname, "r");
if (fp == NULL)
{
printf("Fail to open file!/n");
return 0;
}
printf("Succeed!/n");
return fp;
}
void your_file_close(FILE* fp)
{
fclose(fp);
}
注1:该变种方法的make文件,需要将makefile/make.bat中的MOCK_FLAG及其使用均删除。
注2:参考(3)的makefile,编译时只是对your_file.c使用了MOCK_FLAG,因此,只能将宏定义放在your_file.c文件。
注3:单步执行会发现,对fopen的调用被resolve到对mock_fopen的调用;同样地,对fclose的调用被resolve到对mock_fclose的调用。
注4:若将该宏定义放在my_business.c文件中,则达不到目的,为什么?读者可自行思考。
4.小结
本文通过例子讲述了Mock的基本概念和方法,并提出了两种mock方案,比较而言,方案一较为肤浅,但实现简单;虽然方案二相比方案一利用了编译预处理的宏定义的技巧,但若文件过多,mock文件也会多,从而导致make文件的维护工作增加,或者要添加很多宏定义,也难以维护。
那么有没有一种比较好的方法,能自动产生一些文件或者目录,供测试使用呢?答案是“有,一定有,因为,这个世界从来不缺聪明的发明人”。笔者将在“Mock的基本概念和方法(续)”一文讲解使用Cmock、Unity等工具的方法。
Reference
http://www.mockobjects.com
http://en.wikipedia.org/wiki/Mock_object
http://en.wikipedia.org/wiki/Unit_testing
驱动测试开发>,第7章
Technorati 标签: 单元测试;Mock Objects;Mock
- Mock的基本概念和方法
- Mock的基本概念和方法(续)
- 如何写Fake和Mock的方法
- 单元测试和Mock方法
- 单元测试的mock和stub
- Stub和Mock的区别
- Mock和injectMocks的区别
- mock和fake的区别
- Mock和injectMocks的区别
- mock和fake的区别
- JMockit Mock 私有方法和私有属性
- 软件测试的基本概念和方法 [转]
- 查找的基本概念和简单方法
- PageRank的基本概念和幂迭代方法
- 字典的基本概念和常用方法
- Java的基本概念-方法和变量
- HTTP协议的基本概念和方法
- 5、进程通信的基本概念和方法
- gnustep ubuntu setup.
- 最新版的快递单号生成查询工具 测试版
- [VC/MFC]时间处理
- 浅淡c#中的webbrowser控件
- android中发送短信
- Mock的基本概念和方法
- 方正快速开发平台ES2007数据导入功能问题总结
- FIFO实例
- Multiple-item widgets
- 【方正中间件】用平台如何进行连远程服务器开发(JAVA版本/数据库SQLServer)
- 2011 1-8
- do while(FLASE)的妙用【转】
- 加载spring时applicationContext.xml文件出错
- 软件分层