标签 单元测试 下的文章

CruiseControl.rb初体验

我所在的项目一直以C语言作为主要开发语言,与做Java以及其他新兴语言的人不同,组内的同事似乎对新鲜的东西不是那么感兴趣,也没有主动去研究新鲜事物的意愿和意识。我深为此闹心,看到外面世界中那么多美妙的工具,再也不能坐以待毙了。我一直都是有很多想法的,但是迫于自身精力有限,自己无法全身投入,以前都是交予别人去做的,但是收到的效果都不是很好。认识到这点后,我决定自己动手,丰衣足食。

从心底一直对公司的CMMI流程有所抵触,眼看着外面世界中的Agile Development等轻量级开发过程日益壮大,但自己每天还不得不按照各种繁复的流程去做,真有一种"身在曹营心在汉"的感觉。但是在一个CMMI流程"森严"的公司,又如何才能让大家接受Agile的思想呢?我想让大家看到新实践的与以往不同的成效,大家自然也就能够接受了。在Infoq上,有人也给出了建议:“切莫开口提大名词(例如SCRUM或者XP)。建议以CMMI x级的"自我改进"做旗帜,找到组织中存在浪费的环节,引入最佳实践来消除浪费,没有必要把敏捷挂在嘴边 ”。而持续集成也是文章中建议的首选实践,与我的想法不谋而合。

持续集成,以前不是没有做过,在部门的一个项目组中曾经尝试过,自己编写脚本,完成定时更新代码、构建的任务,后因项目紧张,似乎没有收到良好效果。我也曾经在项目组内推进过做Java的同事进行持续集成的实践,并安排一个同事搭建了CruiseControl的服务器,但后来似乎不了了之,也怪我没有在后期给予充分的重视和压力。有过这些教训后,我时常也在想?推进一个持续集成的实践就这么难么?这回我亲自来做,从C开发人员入手。

持续集成是需要良好的工具支持的,业内最知名的持续集成工具莫过于CruiseControl了,CruiseControl在Java开发领域占据着No.1的地位,而且其设计思想也影响着后续的持续集成工具的开发。但是一想到Java,我第一感觉就是复杂,CruiseControl会不会也像配置一个Web服务器一样那样复杂呢?另外CruiseControl使用Maven + Ant + cvs/subversion的组合,我曾经研究过Ant, Maven,大量的xml配置让我感到很头疼,另外是否与C/C++良好配合也是疑问。这样一来,我就没有继续走CruiseControl这条路,我尝试寻找一种配置简单能快速上手,核心功能也不逊于CruiseControl的工具,Thoughtworks的CruiseControl.rb走入我的视野。

一直关注Thoughtworks,因为以前部门的两位大牛级别的同事都在那任职,另外Thoughtworks推出的产品也的确让我很感兴趣,以前就曾研究过Mingle,只不过Mingle是收费的,而且目前性能还有待提高。CruiseControl.rb(以下称为CC.rb)是Thoughtworks的一款开源作品,其主页上的一小段话很是对我的胃口:"continuous integration isn't rocket science. we keep it simple."。简单,正是我所期望的。

目前CC.rb的最新版本是1.3.0,下载后就是一个压缩包:cruisecontrolrb-1.3.0.tgz。解压后,在你的当前目录下会出现一个cruisecontrolrb-1.3.0的目录。CC.rb的依赖非常之少,你只需要在你的集成服务器上安装上ruby和svn客户端即可,注意:ruby和svn的可执行程序的路径需要添加到你的环境变量的PATH中,否则CC.rb将无法找到ruby和svn。目前CC.rb只是支持Svn,其他版本管理工具尚未得到支持,毕竟CC.rb还年轻,开发时间较少,情有可原。

现在CC.rb已经安装完毕了,没错!CC.rb是用ruby实现的,ruby是脚本类语言,无需编译。你现在就可以进入cruisecontrolrb-1.3.0目录下,执行"cruise –version"(在Unix主机上,你需要先将cruise文件chmod一次,否则cruise将无可执行权限),你会看到:
CruiseControl.rb, version 1.3.0
Copyright (C) 2007 ThoughtWorks

现在你可以添加和配置你的project了。这里要先说一下,CC.rb默认将用户的project数据放在$(HOME)/.cruise/下面,如果你是在Windows平台上,project数据会被默认放在C:\Documents and Settings\USER_NAME\.cruise下面。添加一个proj很简单,你只要提供足够的信息即可:进入到cruisecontrolrb-1.3.0目录下,执行命令:
cruise add PROJ_NAME –url SVN_URL –username USER_NAME –password PASS_WORD,如果你的svn库有权限管理,你需要提供user和passwd。举例: cruise add test_proj –url svn://192.168.0.2:3999/trunk/test_proj –username tony –password tony

这样你就在$(HOME)/.cruise/projects的下面建立了一个test_proj的工程,如果你提供的信息是正确的,CC.rb会在初始建立工程的时候,将你的svn库中的代码checkout出来一份最新的,放在$(HOME)/.cruise/projects/test_proj/work下面。

完成上一步后,$(HOME)/.cruise目录下面有几个配置文件是我们需要重点关注的:
$(HOME)/.cruise
 - config/site_config.rb
 - projects/test_proj/cruise_config.rb

$(HOME)/.cruise/config下的site_config.rb文件是一个全局性的配置文件,无论你在一份CC.rb下建立几个proj,这些proj都会共享该配置,该配置每次修改后都需要重启CC.rb才能生效;该文件中有两个最主要的配置:
1) 邮件服务器配置
如果你想将每次build的结果通过mail的形式发送给相关干系人的话,你就需要配置你的mail server相关信息。
ActionMailer::Base.smtp_settings = {
   :address =>        "xx.xx.xx.xx",
   :port =>           25,   #一般服务器默认smtp端口都是25
   :domain =>         "YOUR_DOMAIN.com",
   :authentication => :plain,
   :user_name =>      "YOUR_USER_NAME",
   :password =>       "YOUR_PASSWD"
 }
2) Dashboard URL
Configuration.dashboard_url = 'http://ip : port'; Dashboard URL将被加入到发给干系人的mail中,这样相关干系人收到mail后可以直接点击该url登录到CC.rb的Dashboard查看相关内容。CC.rb的配置文件也同样适用ruby语言编写的,ruby语言语法较为简单,相信大家都看得懂。

$(HOME)/.cruise/projects/test_proj下的cruise_config.rb文件则是一个Project-specific的local配置文件,它是动态更新的,你的更新在下一次build时是即时生效的。该配置文件中的内容都很重要和实用:
1) project.email_notifier.emails = ['email1@your.site'] 该配置用于添加干系人邮件,这样每次build后,CC.rb都会读取该配置,发送mail给该email list的人;
2) project.email_notifier.from = 'email2@your.site',该配置将指定notify mail中的发件人,你可以因项目的不同而配置不同的mail。
3)  project.rake_task = 'custom',似乎该配置只有当你使用rake这个工具时才需要配置,如果你使用诸如ant,make等工具的话,该配置还需保留原先的被注释状态;
4) project.build_command = 'build_my_app.sh',该配置给予你自定义build脚本的机会,也正是这样,你才可以将CC.rb与Ant, Make等工具集成在一起使用。如:project.build_command = 'make'。注意CC.rb执行build_command的working dir是$(HOME)/.cruise/projects/test_proj/work下面,如果你的build_my_app.sh没有放在work下面,而是在$(HOME)/.cruise/projects/test_proj下面的话,你这块就要配置成:project.build_command = '../build_my_app.sh'; project.build_command与 project.rake_task不能一起使用。
5) project.scheduler.polling_interval = 5.minutes ,CC.rb定期去检测svn是否有new revision,这个配置就是用来指定检测周期的,如果你不配置,那默认是30s。

如果你以上都配置完毕了,你现在就可以启动CC.rb了。启动方法:进入到cruisecontrolrb-1.3.0目录下,执行"cruise start -p port"。CC.rb会启动自带一个轻量级web server – WEBrick ,对于我这个对web server不熟悉的人还是很方便的,我只需要告诉CC.rb端口即可。CC.rb启动后,你可以在浏览器输入http://ip:port,CC.rb的简洁的页面就会显示出来,你会在页面上看到你刚才建立的工程test_proj,点击右上方的"build now"按钮,你就开始了一次build的过程。无论成功还是失败,你都应该收到一封mail,关于此次build的详情可以点击mail中的链接。如果失败了,你可以在页面上的"build log"中看到此次build的详细日志,以帮助你分析失败原因。

CC.rb是如何判断build过程成功还是失败了呢?我们以project.build_command = 'xx'的配置为例,其实CC.rb是通过判断xx这个builder的返回值来决定build过程是否成功的。当xx这个builder返回0,说明build成功;否则build失败;我们不妨测试一下:在$(HOME)/.cruise/projects/test_proj/work下面写一个小程序:
/* test.c */
int main() {
 exit(0);
}
gcc -o test test.c

配置project.build_command = 'test',点击"build now",收到mail,提示build success;如果你将exit(0)改成exit(1),那么一封failed的mail就会发到你的邮箱里。

C/C++没有很好的单元测试工具,一般C/C++单元测试工具都会将单元测试用例编译链接成为一个可执行文件,然后执行,以判断是否通过。到这里你是否想到了如何将C/C++的单元测试与CC.rb集成到一起的方法呢?对,我们通过脚本也好,或者干脆自己编写一个单元测试框架,通过程序返回值告知CC.rb新提交的代码是否通过了单元测试的考验。

以上是第一次使用体验CC.rb时记录下的内容,其实CC.rb就是这么简单!

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 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