标签 Unittest 下的文章

有关单元测试的“只言片语”

今天下午参加了新同事的一次单元测试相关的交流,交流活动尾声,我又带着大家做了一次相关内容的概括性回顾,总结起来都包含在下面这"只言片语"中了,这里贴出来仅供一笑了之^_^:

单元测试归属的范畴 — 开发者的测试
单元测试采用的方法 — 用代码测试代码
单元测试进行的意义 — 开发者的"信心之旅",让自己有信心,让他人放心。
单元测试执行的结果 — “先苦后甜”
单元测试用例的特点 — 与时俱进,不断重构
单元测试用例的寿命 — 消亡于其从属的软件系统“消亡”的那一刻。
单元测试用例的组织 — 以xUNIT工具为导向
单元测试用例的执行 — 追求最大程度的自动化

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的维护任务将有很大加重,实现全工程的单元测试集中还需时日。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 商务合作请联系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