标签 Programmer 下的文章

C单元测试之Mock Test篇

曾经在多篇blog中报怨过:用C语言写业务逻辑实在是让人身心忐忑不安,再加之C语言自有的"特点",让其与"单元测试"始终若即若离,曾经尝试过写了一个轻量级C Unit Testing lib,至少目前我依旧在用,但多用在编写独立算法以及底层库的场合。业务层少有使用。业务层多是遗留系统,当初前辈们设计时对可测性考虑不够周全,导致现在无法很好的将各个部分独立抽出进行测试,虽然我们也在做着类似"重构"的工作,但鉴于规模较大,不能一蹴而就,我们需要一步一步找出使用C应对各种单元测试情况的方法。这里说说Mock Test

在系统中,我们不可避免的要调用一些外部或者系统级别的接口,而我们在测试时这些接口的环境也许并不存在,但是我们测试业务流程时还是要覆盖到所有的,业界就提出了Mock这个概念,最开始在Java开发领域,后来在其他语言中都有引入。Mock是一种什么东西?其实感觉就是给你一个机会,一种模拟和控制外部/系统级别对象或者接口的方法,这样你大可在不必与真实环境交互的前提下就完成所有依赖外部环境的业务流程的覆盖测试。Mock Test在许多语言中都有支持,但是在C语言中,Mock的支持似乎少之又少,在Cgreen这个C Unit test framework中虽然支持Mock,但是其要求你的待测试的业务接口必须附加一个stub参数,这样具有"侵入性"的设计让我感觉很是别扭,而且对于外部接口,你更是无法改变其接口原型,那么能否有其他的方法呢?这里放出一种我的方案,也不甚完美。

C语言有其自身的特色,我这里利用的就是其在编译时的一些特色:先进行预编译,再进行编译和链接。我试着在两个阶段之间做一个trick,以达到我的目的-Mock。

一般我们这样编写一个业务模块:
/* biz.h */
#ifndef BIZ_H
#define BIZ_H

#include

int biz_operation(char *fname);
#endif

/* biz.c */
#include "biz.h"

int biz_operation(char *fname) {
        FILE *fp = NULL;

        fp = fopen(fname, "r");
        if (fp == NULL) {
                printf("fail to open fle!\n");
                return 1;
        } else {
                printf("succeed to open file!\n");
                return 0;
        }
}
这个业务模块有一个业务操作流程,试图只读方式打开一个文件。如果没有mock辅助,我们在测试时需要在环境下手工创建一个文件,这样才能返回"成功",否则一直就是"失败","成功"分支也就无法测试得到。

我们先建立我们的单元测试代码文件:
/* test.c */
#include "biz.h"

void test_biz_operation_return_succ() {
        int rv;
        rv = biz_operation("test-c-mock.txt");
        xx_assert(rv == 0);
}

int main() {
        … …
        /* 无论采用什么单元测试框架,都会有直接或者间接的类似如下调用:*/
        test_biz_operation_return_succ();
        … …
}
biz_operation依赖一个fopen的C标准库调用,我们如何去做一个fopen的mock接口呢?前面说过,我们要利用C的两阶段编译的特色来完成这个mock。我们增加一个mock.h和一个对应的mock.c,我们在这对文件中实现我们自己对fopen的控制。
/* mock.h */
#ifndef MOCK_H
#define MOCK_H

#include

FILE* mock_fopen(char *fname, char *option);
#endif

/* mock.c */
#include "mock.h"

FILE* mock_fopen(char *fname,  char *option) {
        return (FILE*)0×12345678; //在这里,你可以自由控制返回结果
}

如何将mock_fopen与fopen联系在一起呢?我们通过gcc提供的-D选项来做。我们形象化的说一下:
上述源文件的目录结构如下:
/export/home/mock_test
  - biz/
  - test/
我们在test目录下新建一个Makefile文件,用来自动完成test的编译。
## Makefile ##
BIZOBJDIR = /export/home/mock_test/biz
BIZOBJ = $(BIZOBJDIR)/mock_biz.o
BIZSRC = $(BIZOBJDIR)/biz.c

TESTSRC = test.c mock.c
TESTOBJ = test.o mock.o

MOCK_FLAG = -Dfopen=mock_fopen # 关键之处

all:
        gcc $(MOCK_FLAG) -c -o $(BIZOBJ) $(BIZSRC)
        gcc -c $(TESTSRC) -I$(BIZOBJDIR)
        gcc -o test $(TESTOBJ) $(BIZOBJ)

我们从理论上分析一下这种方法的可行性:我们先将biz.c编译成.o文件,MOCK_FLAG将biz.c中的fopen替换成了mock_fopen,这些都是预编译器的功劳;然后我们在test目录下,将test.c 和mock.c编译为对应的.o文件,这里无需使用MOCK_FLAG,否则会有compile error发生;最后一步进行链接:test.o中的biz_operation符号在mock_biz.o中被resolved,而mock_biz.o中的biz_operation在mock.o中被resolved。这样链接后,

fopen处实际上调用的是mock_fopen,也就是那个你可以自由控制的接口。如果在biz.c中还有其他系统调用,比如write, read等,我们都可以

将其mock加到mock.c中,比如称为mock_write和mock_read,然后更新MOCK_FLAG = -Dfopen=mock_fopen -Dwrite=mock_write -Dread=mock_read等。

以上结果已经在Solaris上GCC下测试通过了,目前这种想法还不成够熟,也只是在单个业务模块下做了些测试,下一步如果能做到整个工程的单元测试那就更好了。鉴于上面的情况,如果mock过多,对Makefile的维护任务将有很大加重,实现全工程的单元测试集中还需时日。

软件业的'图纸'在哪里?

上周日和橱柜公司商量好,下午三点到我的房子量尺,橱柜设计师按时到达,拿着一卷尺开始了测量工作。有过装修经历的人都知道:在装修公司进场之前需要橱柜设计师出一份水电改造图,便于装修公司人员确定水电改造的具体方法。装修公司的施工人员与橱柜设计师之间仅需要一份设计图纸就可以完成水电路改造的沟通,这不由得让我想起这样一个问题:"软件开发领域的"图纸"在哪里呢"?

"图纸"是建筑行业的标准的共同语言,它能让设计师与施工者无缝沟通,同时由于建筑图纸的形象性,普通人看了基本也能了解一二,这样普通用户和设计师沟通起来也是很容易的,即使是一个外国设计师设计的图纸,只要使用了标准的图纸符号,中国的施工者也可以完美的将之实现出来,反之中国设计师的作品,外国施工人员也亦然可以实现之。软件行业的人总喜欢拿软件开发与建筑行业做比较,设计模式就是其中最典型的例子。不过这么多年来,软件开发过程仍然无法达到建筑业的那么"精确",这里的精确不是指过程和进度的精确,而是沟通的"精确"。

软件开发领域的"报怨声"已经持续了几十年了,需求分析人员无法获取用户的准确需求、系统设计人员无法将自己的全部设计思想很好的传达给编程实现人员,人们都在抱怨:缺少一种"共同语言",能让他们之间"无缝的沟通"。软件业的大师们绞尽脑汁、费尽心思、提出了多种沟通语言,以试图解决这个问题。这里面最著名的莫过于由OMG组织于20世纪末推出的"统一建模语言(UML)"了,顾名思义,UML试图统一软件开发领域所有过程阶段的"沟通标准",需求阶段有Use Case图;设计阶段有组件图、类图;部署阶段有部署图,另外还有序列图、状态图、活动图作为辅助。UML到目前为止也从1.0发展到了2.0版本,但其实际应用情况如何呢?乐观点说是:没有"图纸"在建筑领域应用的那么广;如果从我了解的和接触的实际情况来看,UML已经过了其高峰期,开始变得"不瘟不火"。

程序员多数喜欢简单化、个性化和形象化的表达思想,一块白板,无拘无束。虽然UML在形象化上做的还不错,但是却始终无法打动程序员的心扉。从另一个事实来讲,图纸是建筑行业的门槛或者说是基础,而与之对应,代码才是软件行业的门槛。这样一来似乎代码才应该是一种共同的语言,在"敏捷软件开发"的附录中就有这样一篇文章:"源代码即设计"。那是否说:"代码"就是"软件业的图纸"呢?"代码"在设计开发人员之间可以说是基本无缝的,但是对于普通客户而言,代码似乎太专业了。其实在建筑行业"图纸"也多在设计师和设计师、设计师与施工人员之间做沟通之用,大项目中少有客户与设计师沟通时使用"图纸",多数会用外观效果图。这样一来似乎软件开发领域的"代码"与建筑业"图纸"的概念达到了一定的一致性。"代码"作为沟通媒介的前提是:代码和设计的统一。为了达到这一目的,需要代码结构清晰,可读性,可沟通性要好,这也势必需要实现人员提高编码技艺。都说编程是一门艺术,从这里来看,名符其实。如果承认"代码"这一地位,那么实际上是确定了一个方向,以后向这个方向努力就是了,众所周知的"敏捷"在这个方向上做出了努力。

上面也已经说过,"代码"过于专业,并不能作为"统一沟通媒介"来统一整个软件开发过程,看来我们只能继续期待头脑中理想的"图纸"的出现了,也许它会诞生于将来的一个突破性的"发明"或"发现“;也可能它将是一个永远的"梦"。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 Go语言精进之路1 Go语言精进之路2 Go语言第一课 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats