标签 CBehave 下的文章

buildc 0.3.0版本发布

buildc正式在项目中应用以来,我们收到了许多同事针对buildc演进的意见和建议。其中确实有些易用性的问题是在最初设计时未考虑周全的,尤其是.buildc.rc中的配置,同事们对该文件的配置已经“怨声载道”了。

.buildc.rc是用来配置某开发者在开发过程中使用的第三方库所在subversion repository信息的,例如:

a_repository = ('SVN库地址', '本地缓存路径',
              [
                  # 格式:[(“第三方库名称”, “库版本”, “特征库文件”), …]
                  ('libevent', '2.0.10', 'lib/libevent.a'),
                  ('instantclient', '10.2.0.5.0', 'lib/libnnz10.so'),
                  …
              ]
            )
b_repository = ('SVN库地址', '本地缓存路径', [])
c_repository = ('SVN库地址', '本地缓存路径', [])

external_repositories = [
                        a_repository,
                        b_repository,
                        c_repository,
                        …
                   ]

这里面需要维护最多、最频繁的就是各个repository中具备的第三方库名、版本号。开发者所开发的项目所依赖的第三方库信息发生变化,不仅仅需要修 改project下的buildc.cfg文件,还可能要修改.buildc.rc,大家维护起来确实体验不好,会多耗费一些工作量。

针对这个主要问题,我们决定对buildc进行一次较大范围的重构,重构后的版本定为buildc 0.3.0版本。以下是buildc 0.3.0版本的主要改动点:

一、简化.buildc.rc的配置,重新定义cache相关命令的语义

0.3.0及以后版本的.buildc.rc只需配置repository的地址信息以及cache缓存的本地路径信息,无需再提供repository 里面具体的第三方库以及版本号信息了,这样一来,大多数情况下,project依赖的第三方库发生变更,都无需修改.buildc.rc了。

a_repository = ('SVN库地址', '本地缓存路径')
b_repository = ('SVN库地址', '本地缓存路径')
c_repository = ('SVN库地址', '本地缓存路径')

external_repositories = [
                        a_repository,
                        b_repository,
                        c_repository,
                        …
                   ]

随之而变的是buildc cache相关命令的语义,0.3.0中cache相关命令的语义如下:

* buildc cache init - 生成.buildc.repository,该文件是svn库的目录结构文件,相当于一份svn repository内部的地图,repository中存放的各种第三方库以及版本均在该文件中索引;如果该文件已经存在,命令执行的结果为:提示已存在。

* buildc cache upgrade – 根据.buildc.rc的最新更新,重新生成.buildc.repository文件,并将该文件中所有lib本地的 Revision号置为none。该文件并不会执行本地cache的library的真实更新操作。

* buildc cache update  - 
    1. 如果.buildc.rc已经修改,但没有执行buildc cache upgrade,update会对比本地缓存库信息与对应的.repository文件中的同名lib信息,如果不一致,则提醒执行upgrade。
    2. 如果.buildc.repository是新生成的,所有lib本地的Revision号均是none,则提示没有要更新的本地缓存库;
    3. 如果某个项目已经download了自己依赖的库,那update将比对svn库中和本地库的revision差异,并下载最新库版本。并修改.buildc.repository中对应库的本地revision number。

* buildc cache remove – 将.buildc.repository中对应库的本地revision number都置为none,并删除本地缓存的库文件。

二、重新定义config make的语义

前面提到了,在执行buildc cache init时,buildc只是负责生成.repository文件,而并不真实执行库文件的下载和缓存。那何时真正下载呢?答案是在执行buildc config make时。这里颇有些“lazy evaluate”的味道,需要时再“download and cache it"。

* buildc config make

1. 如果.buildc.rc已经修改,但没有执行buildc cache upgrade,config make会对比本地缓存库信息与对应的.repository文件中的同名lib信息,如果不一致,则提醒执行upgrade。
2. 如果.buildc.rc是新生成的,或执行cache upgrade后的,config make会根据project对应的buildc.cfg中配置的第三方库,在.buildc.repository中查找是否存在(包括对应的版本 号),如果存在,则从subversion server端自动下载;否则提示出错。
3. 如果本地缓存中某个库文件不存在,buildc config make会检测到,并自动下载该库,并cache起来。
4. 如果subversion端某个库的svn revision号发生的更新,buildc config make会检测到,并下载最新的版本。

总之一切都是在buildc config make时来完成的,按需下载或更新,这样你甚至无需进行手工的library Cache维护。

三、转向OO范型

实现buildc 0.3.0的小同事(wtz1989227@gmail.com)对OO情有独钟,因此在这个版本中,他将以前的结构化代码做了大幅度调整,并用OO的方 式进行了重构。按照wtz的思路,这次改造比较初级,OOD做得还不够充分,以后慢慢调整。实际代码中反映出来的情况也的确是这样。

四、buildc 0.2.3发布

在将buildc 0.3.0代码merge到trunk之前,我创建了buildc-0.2的maintain branch,虽然理论上buildc 0.3.0在功能和配置方面与buildc 0.2.x版本是兼容的,但毕竟代码调整幅度较大。另外建议大家都转移到0.3.0这个最新版本上来,buildc-0.2分支顶多做一些bugfix, 不会再有新feature添加进去了。

昨天在发布buildc 0.3.0的同时,还发布了buildc-0.2的一个Bugfix版 – buildc 0.2.3,该版本主要做了如下一些fix:

  * 执行cache upgrade时增加对.buildc.rc中repository特征文件存在性的检查;
  * 执行config make时增加对Make.rules文件是否为空的判断;
  * 执行pack source时,添加VERSION文件,记录打包的上下文信息。

五、其它

考虑到github的活跃度远远高于google code,加上google code最近访问十分不稳定,因此之前就将buildc(还有cbehavelcut以及我的实验代码库)fork了一份到github上了,也攒攒 github上人气,因此这次buildc 0.2.3和buildc 0.3.0的代码还要发布到github上一次。git工具平时用的少,尤其是提交代码到github,这次算入门了。

* 代码远程提交
用git remote add一个github的remote repository后,就可以使用git push origin master将本地的commit推送到github上了。

* 打tag,并推送tag

   — 查看Tag的git命令是git tag
   — 本地打tag,用这个命令: git tag -a v0.2.3 -m"0.2.3 released"
   — 推送Tag到remote repository:git push –tags origin master,不加–tags是无法推送tag的。

* branch操作
  — 查看branch:git branch
  — 创建branch:git branch buildc-0.2
  — 推送branch:git push origin buildc-0.2
  — 本地切换branch:git checkout buildc-0.2

CBehave – 一个C语言行为驱动开发框架

Behaviour-Driven Development,即行为驱动开发在业界早已不是什么新鲜玩意了。我之前也略有了解,不过一直没有"深入钻研"。直到今年年初InfoQ的几篇有关BDD的文章才让我对BDD有了更多的认识。与TDD一样,C语言在BDD领域依旧是一个"后进分子",在多数主流语言(Java,C#,Ruby等)都已经拥有比较成熟的BDD框架(如JBehaveSpecFlowCucumber)的今天,C语言却似乎仅有一款BDD框架-CSpec可用。于是年初的时候我就把设计和实现一个用于C语言的行为驱动开发框架加入到我今年的ToDoList中了。

在确定好目标的同时,我也给这款框架命名为CBehave(模仿JBehave),并在Google Code上建立了CBehave的托管项目。但人的时间和精力总是有限的,直到8月中旬我才开始着手进行这个框架的设计和实现。设计和实现一款给程序员使用的工具,这本身就是一件让人兴奋的事情。我先是通过DanNorth的博文"Introducing BDD"(其中译版在这里)了解了BDD的"诞生历程",然后又广泛地了解了一下其他语言的BDD框架。对于CSpec这一目前唯一的C语言BDD框架,我并不想给予过多评价,不过总体来说和其他语言的BDD框架相比,CSpec有些简陋,应该说还无法很好的支持BDD中一些核心思想的表达,并且目前它还不支持Mock。这些都坚定了我重新实现一个C语言BDD框架的决心,起码我不完全是"重新发明轮子"^_^。

作为后来者,CBehave的设计参考了诸多现有的主流BDD框架,其中直接灵感来源于Cucumber,不过由于C语言静态编译语言的本质,CBehave与Cucumber在大多地方也只是形似而已。作为一篇CBehave的介绍性文章,这里列举一些CBehave的主要特点:

首先,CBehave借鉴Cucumber的设计采用Feature + Scenario结合的方式来描述功能需求(DanNorth: 需求也是行为),并且在每个Scenario内部采用BDD的经典的GIVEN-WHEN-THEN结构描述行为的验收标准(acceptance criteria)。

这里给出一个总体的行为描述模板:

FEATURE #1
    SCENARIO #1
        GIVEN
            … …
        WHEN
            … …
        THEN
            … …

    SCENARIO #2
        GIVEN
            … …
        WHEN
            … …
        THEN
            … …

    … …

FEATURE #2
… … 

FEATURE #n

原则上FEATURE之间是相互隔离的;FEATURE内部的多个Scenario之间在代码定义和执行时也是相互隔离,互不干扰的。这是一个使用该框架的基本约束。不过这就好比建议性锁,全靠使用时的自觉,否则很容易造成框架运行出错。

下面是一个真实的使用CBehave对strstr函数进行测试的例子(代码片断,完整例子参见源码cbehave/src/example/string_test.c):

FEATURE(1, "strstr")
    SCENARIO("The strstr finds the first occurrence of the substring in the source string")

        GIVEN("A source string: [Lionel Messi is a great football player]")
            char *str = "Lionel Messi is a great football player";
        GIVEN_END

        WHEN("we use strstr to find the first occurrence of [football]")
            char *p = strstr(str, "football");
        WHEN_END

        THEN("We should get the string: [football player]")
            SHOULD_STR_EQUAL(p, "football player");
        THEN_END

    SCENARIO_END

    SCENARIO("If strstr could not find the first occurrence of the substring, it will return NULL")

        GIVEN("A source string: FC Barcelona is a great football club.")
            char *str = "FC Barcelona is a great football club";
        GIVEN_END

        WHEN("we use strstr to find the first occurrence of [AC Milan]")
            char *p = strstr(buf, "AC Milan");
        WHEN_END

        THEN("We should get no string but a NULL")
            SHOULD_STR_EQUAL(p, NULL);
        THEN_END
    SCENARIO_END
FEATURE_END

int main() {
    cbehave_feature strstr_features[] = {
        {feature_idx(1)},
    };

    return cbehave_runner("Strstr Features are as belows:", strstr_features);
}

编译运行这个测试,我们会得到如下结果(节选):

Strstr Features are as belows:

Feature: strstr
 Scenario: The strstr finds the first occurrence of the substring in the source string
  Given: A source string: Lionel Messi is a great football player
  When: we use strstr to find the first occurrence of [football]
  Then: We should get the string: [football player]
 Scenario: If strstr could not find the first occurrence of the substring, it will return NULL.
  Given: A source string: FC Barcelona is a great football club.
  When: we use strstr to find the first occurrence of [AC Milan]
  Then: We should get no string but a NULL

Summary:
 total features: [1]
 failed features: [0]
 total scenarios: [2]
 failed scenarios: [0]

CBehave将strstr的行为原汁原味地输出到最终的测试结果中,与xUnit等框架相比,这确是一个进步,我们在获知测试结果的同时,还依稀中看到了这个特性的需求描述,前提是你要给出一个很好很精确的描述,但这已经不是框架可以帮助你做的了^_^。另外即使你的测试失败了,你甚至可以不通过错误提示中的源码文件名和行号信息也可以快速定位到错误的位置所在。因为错误周围是有足够的上下文信息的。

其次,CBehave支持mock。CBehave中mock的实现完全参考了我之前设计的单元测试框架LCUT,下面是一个简单的例子(片断,完整代码参加cbehave/src/example/product_database_test.c):

FEATURE(1, "Get the total count of employees")
    SCENARIO("Get the total count of employees")
        GIVEN("The db connection is ready and there are 5 employees in total");
            CBEHAVE_RETV_RETURN(connect_to_database, 0×1234);
            CBEHAVE_ARG_RETURN(table_row_count, 5);
            CBEHAVE_RETV_RETURN(table_row_count, 0);
        GIVEN_END

        WHEN("We call function: get_total_count_of_employee");
            int count = get_total_count_of_employee();
        WHEN_END

        THEN("The total count of employees we read from db should be 5")
            SHOULD_INT_EQUAL(count, 5);
        THEN_END

    SCENARIO_END
FEATURE_END

最后,CBeahve还支持多种SHOULD_XX宏,并且可根据需要灵活添加。目前已支持整型、字符串以及布尔类型的判定。

关于CBehave的实现历程这里也简单说一下。

首先需要确定CBehave的"长相",也就是CBehave采用的行为描述模板是啥样子的。这块儿确是花了我不少的时间,查看各种资料以及研究其他框架的设计,最终选择了Feature+Scenario以及用Given-When-Then来描述行为的"文档模板"。

其次,有了"文档模板"后如何将其转换为可执行的代码实体?这块也费了我不少脑细胞。思前想后,最终设计是将Feature转换成一个可运行的实体-函数。另外我在函数中使用{}来物理划分Scenario,{}可以隔离变量的可见性和作用域,已达到多个Scenarios定义和执行互不干扰的目的。

上面的标准文档结果中的一个FEATURE宏展开后的样子大致是这样的:

static void cbehave_feature_n(void *_state) {
    cbehave_state _old_state;
    cbehave_feature_entry(…, &_old_state, _state);

    {
        /* Scenario #1 */
        int _scenario_state = 0; \
        cbehave_scenario_entry(x, _state);

        … …
        cbehave_scenario_exit(&_scenario_state, _state);
    }

    {
        /* Scenario #2 */

    }

    … …
    {
        /* Scenario #n */

    }

_feature_over: \
    cbehave_feature_exit(…);
}

关于feature函数的命名,我考虑了很长时间,由于从外界输入的信息无法约束,这里我引入了Feature序号,并同时将序号作为Feature函数名的一部分。例如:
FEATURE(10, "fopen features")
展开后的feature函数名就是cbehave_feature_10,这样做对用户也有一定约束,但约束较小,只需CBehave用户保证各个Feature的序号不同即可。

在使用CBehave时,很可能出现另外一个问题:那就是测试代码在GIVEN或WHEN区段中依赖的一些资源申请或其他代码的初始化可能失败或出现异常。遇到这种情况时,用户多选择return或exit。但一旦用户这样做,CBehave就无法统计和输出测试失败情况或统计的不够准确了。为了尽量保证CBehave统计的精确性,CBehave提供了一个宏FEATURE_RETURN供用户使用。FEATURE_RETURN将控制权转移到Feature函数的末尾,也就是上面宏展开末尾的哪个_feature_over跳转标识符处,这样Feature有机会把这一错误情况记录下来。另外还可以保证其他Feature测试的继续运行。这里举个简单例子(完整代码参见cbehave/src/example/text_editor_test.c):

FEATURE(1, "Text Editor – Open Exsited File")
    SCENARIO("Open an Exsited File and write something to it")

        GIVEN("A file named foo.txt")
            FILE *fp = NULL;
            char *buf = "Hello Cbehave!";
        GIVEN_END

        WHEN("we open the file and write something to it")
            fp = fopen("foo.txt", "r+");
            if (!fp)
                FEATURE_RETURN(errno);
        WHEN_END

        THEN("We should see [Hello Cbehave] has been written into foo.txt")
            if (fp)
                fclose(fp);
        THEN_END
    SCENARIO_END
FEATURE_END

例子中如果fopen打开foo.txt失败,代码中调用了FEATURE_RETURN来应对这一情况,而不是直接调用return或exit。

最后,与LCUT不同,Cbehave会尝试运行完所有Feature的测试,而不是遇到测试错误就停止运行。

因为之前有过LCUT单元测试框架的设计和实现经验,这次CBehave框架的设计和实现就相对容易了些。CBehave的设计用了一天时间,上周末两天"百忙"中抽空完成了编码和测试,目前已提供了cbehave-0.1.0-beta版供下载体验,欢迎大家提出你的宝贵意见和建议^_^,更多关于CBehave使用方面的细节请参考CBehave用户指南(http://code.google.com/p/cbehave/wiki/CBehave_User_Guide_cn)。

BTW,我并不是一个纯粹的TDDers。我个人认为完全采用TDD或是BDD还是有一定局限的。是否采用这些方式进行开发还要视产品(或项目)的时间、质量、人员能力等诸多制约因素而定。个人推断国内的C程序员多普遍缺乏采用框架进行单元测试的意识,BDD或TDD的推广还是任重道远的。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 AI原生开发工作流实战 从 0 开始构建 Agent Harness 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