<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; CBehave</title>
	<atom:link href="http://tonybai.com/tag/cbehave/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Mon, 08 Jun 2026 23:32:23 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>buildc 0.3.0版本发布</title>
		<link>https://tonybai.com/2013/05/11/buildc-0-3-0-release/</link>
		<comments>https://tonybai.com/2013/05/11/buildc-0-3-0-release/#comments</comments>
		<pubDate>Sat, 11 May 2013 00:46:42 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Buildc]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CBehave]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Subversion]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[工作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[程序员]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=1268</guid>
		<description><![CDATA[自buildc正式在项目中应用以来，我们收到了许多同事针对buildc演进的意见和建议。其中确实有些易用性的问题是在最初设计时未考虑周全的，尤其是.buildc.rc中的配置，同事们对该文件的配置已经&#8220;怨声载道&#8221;了。 .buildc.rc是用来配置某开发者在开发过程中使用的第三方库所在subversion repository信息的，例如： a_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; [ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; # 格式：[(&#8220;第三方库名称&#8221;, &#8220;库版本&#8221;, &#8220;特征库文件&#8221;), &#8230;] &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (&#39;libevent&#39;, &#39;2.0.10&#39;, &#39;lib/libevent.a&#39;), &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (&#39;instantclient&#39;, &#39;10.2.0.5.0&#39;, &#39;lib/libnnz10.so&#39;), &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#8230; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ] &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ) b_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, []) c_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, []) &#8230; external_repositories = [ &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; a_repository, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; b_repository, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; c_repository, &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; &#8230; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; ] 这里面需要维护最多、最频繁的就是各个repository中具备的第三方库名、版本号。开发者所开发的项目所依赖的第三方库信息发生变化，不仅仅需要修 改project下的buildc.cfg文件，还可能要修改.buildc.rc，大家维护起来确实体验不好，会多耗费一些工作量。 [...]]]></description>
			<content:encoded><![CDATA[<p>自<a href="http://tonybai.com/2011/12/08/buildc-a-building-assistant-tool-for-c-app/">buildc</a>正式在项目中应用以来，我们收到了许多同事针对<a href="http://code.google.com/p/buildc">buildc</a>演进的意见和建议。其中确实有些易用性的问题是在最初设计时未考虑周全的，尤其是.buildc.rc中的配置，同事们对该文件的配置已经&ldquo;怨声载道&rdquo;了。</p>
<p>.buildc.rc是用来配置某开发者在开发过程中使用的第三方库所在subversion repository信息的，例如：</p>
<p><font face="Courier New">a_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 格式：[(&ldquo;第三方库名称&rdquo;, &ldquo;库版本&rdquo;, &ldquo;特征库文件&rdquo;), &hellip;]<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (&#39;libevent&#39;, &#39;2.0.10&#39;, &#39;lib/libevent.a&#39;),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (&#39;instantclient&#39;, &#39;10.2.0.5.0&#39;, &#39;lib/libnnz10.so&#39;),<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; )<br />
	b_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, [])<br />
	c_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;, [])<br />
	&hellip;<br />
	external_repositories = [<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &hellip;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]</font></p>
<p>这里面需要维护最多、最频繁的就是各个repository中具备的第三方库名、版本号。开发者所开发的项目所依赖的第三方库信息发生变化，不仅仅需要修 改project下的buildc.cfg文件，还可能要修改.buildc.rc，大家维护起来确实体验不好，会多耗费一些工作量。</p>
<p>针对这个主要问题，我们决定对buildc进行一次较大范围的重构，重构后的版本定为<a href="https://buildc.googlecode.com/files/buildc-0.3.0.tar.gz">buildc 0.3.0版本</a>。以下是buildc 0.3.0版本的主要改动点：</p>
<p><b>一、简化.buildc.rc的配置，重新定义cache相关命令的语义</b></p>
<p>0.3.0及以后版本的.buildc.rc只需配置repository的地址信息以及cache缓存的本地路径信息，无需再提供repository 里面具体的第三方库以及版本号信息了，这样一来，大多数情况下，project依赖的第三方库发生变更，都无需修改.buildc.rc了。</p>
<p><font face="Courier New">a_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;)<br />
	b_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;)<br />
	c_repository = (&#39;SVN库地址&#39;, &#39;本地缓存路径&#39;)<br />
	&hellip;<br />
	external_repositories = [<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c_repository,<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &hellip;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]</font></p>
<p>随之而变的是buildc cache相关命令的语义，0.3.0中cache相关命令的语义如下：</p>
<p><i>* buildc cache init </i>- 生成.buildc.repository，该文件是svn库的目录结构文件，相当于一份svn repository内部的地图，repository中存放的各种第三方库以及版本均在该文件中索引；如果该文件已经存在，命令执行的结果为：提示已存在。</p>
<p><i>* buildc cache upgrade</i> &#8211; 根据.buildc.rc的最新更新，重新生成.buildc.repository文件，并将该文件中所有lib本地的 Revision号置为none。该文件并不会执行本地cache的library的真实更新操作。</p>
<p><i>* buildc cache update</i>&nbsp; -&nbsp;<br />
	&nbsp;&nbsp;&nbsp; 1. 如果.buildc.rc已经修改，但没有执行buildc cache upgrade，update会对比本地缓存库信息与对应的.repository文件中的同名lib信息，如果不一致，则提醒执行upgrade。<br />
	&nbsp;&nbsp;&nbsp; 2. 如果.buildc.repository是新生成的，所有lib本地的Revision号均是none，则提示没有要更新的本地缓存库；<br />
	&nbsp;&nbsp;&nbsp; 3. 如果某个项目已经download了自己依赖的库，那update将比对svn库中和本地库的revision差异，并下载最新库版本。并修改.buildc.repository中对应库的本地revision number。</p>
<p>* <i>buildc cache remove</i> &#8211; 将.buildc.repository中对应库的本地revision number都置为none，并删除本地缓存的库文件。</p>
<p><b>二、重新定义config make的语义</b></p>
<p>前面提到了，在执行buildc cache init时，buildc只是负责生成.repository文件，而并不真实执行库文件的下载和缓存。那何时真正下载呢？答案是在执行buildc config make时。这里颇有些&ldquo;lazy evaluate&rdquo;的味道，需要时再&ldquo;download and cache it&quot;。</p>
<p>* <em>buildc config make</em></p>
<p>1. 如果.buildc.rc已经修改，但没有执行buildc cache upgrade，config make会对比本地缓存库信息与对应的.repository文件中的同名lib信息，如果不一致，则提醒执行upgrade。<br />
	2. 如果.buildc.rc是新生成的，或执行cache upgrade后的，config make会根据project对应的buildc.cfg中配置的第三方库，在.buildc.repository中查找是否存在（包括对应的版本 号），如果存在，则从subversion server端自动下载；否则提示出错。<br />
	3. 如果本地缓存中某个库文件不存在，buildc config make会检测到，并自动下载该库，并cache起来。<br />
	4. 如果subversion端某个库的svn revision号发生的更新，buildc config make会检测到，并下载最新的版本。</p>
<p>总之一切都是在buildc config make时来完成的，按需下载或更新，这样你甚至无需进行手工的library Cache维护。</p>
<p><b>三、转向OO</b><b>范型</b></p>
<p>实现buildc 0.3.0的小同事(wtz1989227@gmail.com)对OO情有独钟，因此在这个版本中，他将以前的结构化代码做了大幅度调整，并用OO的方 式进行了重构。按照wtz的思路，这次改造比较初级，OOD做得还不够充分，以后慢慢调整。实际代码中反映出来的情况也的确是这样。</p>
<p><b>四、</b><b>buildc 0.2.3发布</b></p>
<p>在将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添加进去了。</p>
<p>昨天在发布buildc 0.3.0的同时，还发布了buildc-0.2的一个Bugfix版 &#8211; <a href="https://buildc.googlecode.com/files/buildc-0.2.3.tar.gz">buildc 0.2.3</a>，该版本主要做了如下一些fix：</p>
<p>&nbsp; * 执行cache upgrade时增加对.buildc.rc中repository特征文件存在性的检查；<br />
	&nbsp; * 执行config make时增加对Make.rules文件是否为空的判断；<br />
	&nbsp; * 执行pack source时，添加VERSION文件，记录打包的上下文信息。</p>
<p><b>五、其它</b></p>
<p>考虑到<a href="http://github.com">github</a>的活跃度远远高于<a href="http://code.google.com">google code</a>，加上google code最近访问十分不稳定，因此之前就将<a href="https://github.com/bigwhite/buildc">buildc</a>（还有<a href="https://github.com/bigwhite/cbehave">cbehave</a>、<a href="https://github.com/bigwhite/lcut">lcut</a>以及我的<a href="https://github.com/bigwhite/experiments">实验代码库</a>）fork了一份到github上了，也攒攒 github上人气，因此这次buildc 0.2.3和buildc 0.3.0的代码还要发布到github上一次。git工具平时用的少，尤其是提交代码到github，这次算入门了。</p>
<p>* 代码远程提交<br />
	用git remote add一个github的remote repository后，就可以使用git push origin master将本地的commit推送到github上了。</p>
<p>* 打tag，并推送tag</p>
<p>&nbsp;&nbsp; &#8212; 查看Tag的git命令是<font face="Courier New">git tag</font>；<br />
	&nbsp;&nbsp; &#8212; 本地打tag，用这个命令： <font face="Courier New">git tag -a v0.2.3 -m&quot;0.2.3 released&quot;</font>；<br />
	&nbsp;&nbsp; &#8212; 推送Tag到remote repository：<font face="Courier New">git push &#8211;tags origin master</font>，不加&#8211;tags是无法推送tag的。</p>
<p>* branch操作<br />
	&nbsp; &#8212; 查看branch：<font face="Courier New">git branch</font><br />
	&nbsp; &#8212; 创建branch：<font face="Courier New">git branch buildc-0.2</font><br />
	&nbsp; &#8212; 推送branch：<font face="Courier New">git push origin buildc-0.2</font><br />
	&nbsp; &#8212; 本地切换branch：<font face="Courier New">git checkout buildc-0.2</font></p>
<p style='text-align:left'>&copy; 2013, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2013/05/11/buildc-0-3-0-release/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CBehave &#8211; 一个C语言行为驱动开发框架</title>
		<link>https://tonybai.com/2011/08/15/cbehave-a-bdd-framework-for-c/</link>
		<comments>https://tonybai.com/2011/08/15/cbehave-a-bdd-framework-for-c/#comments</comments>
		<pubDate>Mon, 15 Aug 2011 05:18:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BDD]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CBehave]]></category>
		<category><![CDATA[CSpec]]></category>
		<category><![CDATA[JBehave]]></category>
		<category><![CDATA[JUnit]]></category>
		<category><![CDATA[LCUT]]></category>
		<category><![CDATA[Opensource]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[TDD]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编码]]></category>
		<category><![CDATA[行为驱动开发]]></category>

		<guid isPermaLink="false">http://tonybai.com/2011/08/15/cbehave-%e4%b8%80%e4%b8%aac%e8%af%ad%e8%a8%80%e8%a1%8c%e4%b8%ba%e9%a9%b1%e5%8a%a8%e5%bc%80%e5%8f%91%e6%a1%86%e6%9e%b6/</guid>
		<description><![CDATA[<p>Behaviour-Driven Development，即行为驱动开发在业界早已不是什么新鲜玩意了。我之前也 略有了解，不过一直没有"深入钻研"。直到今年年初InfoQ的几篇有关BDD的文章才让我对BDD有了更多的认识。与TDD一样，C语言在BDD领域依旧是一个"后进分子"，在多数主流语言(Java...</p>]]></description>
			<content:encoded><![CDATA[<p><a href="http://behaviour-driven.org/" target="_blank">Behaviour-Driven Development</a>，即行为驱动开发在业界早已不是什么新鲜玩意了。我之前也略有了解，不过一直没有&quot;深入钻研&quot;。直到今年年初InfoQ的几篇有关BDD的文章才让我对BDD有了更多的认识。与<a href="http://en.wikipedia.org/wiki/Test-driven_development" target="_blank">TDD</a>一样，C语言在BDD领域依旧是一个&quot;后进分子&quot;，在多数主流语言(Java，C#，Ruby等)都已经拥有比较成熟的BDD框架(如<a href="http://jbehave.org/" target="_blank">JBehave</a>、<a href="http://specflow.org/" target="_blank">SpecFlow</a>和<a href="http://cukes.info/" target="_blank">Cucumber</a>)的今天，C语言却似乎仅有一款BDD框架-<a href="https://github.com/arnaudbrejeon/cspec" target="_blank">CSpec</a>可用。于是年初的时候我就把设计和实现一个用于C语言的行为驱动开发框架加入到我今年的ToDoList中了。</p>
<p>在确定好目标的同时，我也给这款框架命名为<a href="http://code.google.com/p/cbehave" target="_blank">CBehave</a>(模仿JBehave)，并在Google Code上建立了CBehave的托管项目。但人的时间和精力总是有限的，直到8月中旬我才开始着手进行这个框架的设计和实现。设计和实现一款给程序员使用的工具，这本身就是一件让人兴奋的事情。我先是通过DanNorth的博文&quot;<a href="http://dannorth.net/introducing-bdd/" target="_blank">Introducing BDD</a>&quot;(其中译版在<a href="http://tonybai.com/2011/08/10/introducing-bdd/" target="_blank">这里</a>)了解了BDD的&quot;诞生历程&quot;，然后又广泛地了解了一下其他语言的BDD框架。对于CSpec这一目前唯一的C语言BDD框架，我并不想给予过多评价，不过总体来说和其他语言的BDD框架相比，CSpec有些简陋，应该说还无法很好的支持BDD中一些核心思想的表达，并且目前它还不支持<a href="http://tonybai.com/2008/04/12/mock-test-in-c-unit-test/" target="_blank">Mock</a>。这些都坚定了我重新实现一个C语言BDD框架的决心，起码我不完全是&quot;重新发明轮子&quot;^_^。</p>
<p>作为后来者，CBehave的设计参考了诸多现有的主流BDD框架，其中直接灵感来源于Cucumber，不过由于C语言静态编译语言的本质，CBehave与Cucumber在大多地方也只是形似而已。作为一篇CBehave的介绍性文章，这里列举一些CBehave的主要特点：</p>
<p>首先，CBehave借鉴Cucumber的设计采用Feature + Scenario结合的方式来描述功能需求(DanNorth: 需求也是行为)，并且在每个Scenario内部采用BDD的经典的GIVEN-WHEN-THEN结构描述行为的验收标准(acceptance criteria)。</p>
<p>这里给出一个总体的行为描述模板：</p>
<p>FEATURE #1<br />
	&nbsp;&nbsp;&nbsp; SCENARIO #1<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;</p>
<p>&nbsp;&nbsp;&nbsp; SCENARIO #2<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;</p>
<p>&nbsp;&nbsp;&nbsp; &#8230; &#8230;</p>
<p>FEATURE #2<br />
	&#8230; &#8230;&nbsp;</p>
<p>FEATURE #n</p>
<p>原则上FEATURE之间是相互隔离的；FEATURE内部的多个Scenario之间在代码定义和执行时也是相互隔离，互不干扰的。这是一个使用该框架的基本约束。不过这就好比建议性锁，全靠使用时的自觉，否则很容易造成框架运行出错。</p>
<p>下面是一个真实的使用CBehave对strstr函数进行测试的例子(代码片断，完整例子参见源码cbehave/src/example/string_test.c)：</p>
<p>FEATURE(1, &quot;strstr&quot;)<br />
	&nbsp;&nbsp;&nbsp; SCENARIO(&quot;The strstr finds the first occurrence of the substring in the source string&quot;)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN(&quot;A source string: [Lionel Messi is a great football player]&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *str = &quot;Lionel Messi is a great football player&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN(&quot;we use strstr to find the first occurrence of [football]&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *p = strstr(str, &quot;football&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN(&quot;We should get the string: [football player]&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SHOULD_STR_EQUAL(p, &quot;football player&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN_END</p>
<p>&nbsp;&nbsp;&nbsp; SCENARIO_END</p>
<p>&nbsp;&nbsp;&nbsp; SCENARIO(&quot;If strstr could not find the first occurrence of the substring, it will return NULL&quot;)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN(&quot;A source string: FC Barcelona is a great football club.&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *str = &quot;FC Barcelona is a great football club&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN(&quot;we use strstr to find the first occurrence of [AC Milan]&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *p = strstr(buf, &quot;AC Milan&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN(&quot;We should get no string but a NULL&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SHOULD_STR_EQUAL(p, NULL);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN_END<br />
	&nbsp;&nbsp;&nbsp; SCENARIO_END<br />
	FEATURE_END</p>
<p>int main() {<br />
	&nbsp;&nbsp;&nbsp; cbehave_feature strstr_features[] = {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {feature_idx(1)},<br />
	&nbsp;&nbsp;&nbsp; };</p>
<p>&nbsp;&nbsp;&nbsp; return cbehave_runner(&quot;Strstr Features are as belows:&quot;, strstr_features);<br />
	}</p>
<p>编译运行这个测试，我们会得到如下结果(节选)：</p>
<p>Strstr Features are as belows:</p>
<p>Feature: strstr<br />
	&nbsp;Scenario: The strstr finds the first occurrence of the substring in the source string<br />
	&nbsp;&nbsp;Given: A source string: Lionel Messi is a great football player<br />
	&nbsp;&nbsp;When: we use strstr to find the first occurrence of [football]<br />
	&nbsp;&nbsp;Then: We should get the string: [football player]<br />
	&nbsp;Scenario: If strstr could not find the first occurrence of the substring, it will return NULL.<br />
	&nbsp;&nbsp;Given: A source string: FC Barcelona is a great football club.<br />
	&nbsp;&nbsp;When: we use strstr to find the first occurrence of [AC Milan]<br />
	&nbsp;&nbsp;Then: We should get no string but a NULL</p>
<p>Summary:<br />
	&nbsp;total features: [1]<br />
	&nbsp;failed features: [0]<br />
	&nbsp;total scenarios: [2]<br />
	&nbsp;failed scenarios: [0]</p>
<p>CBehave将strstr的行为原汁原味地输出到最终的测试结果中，与xUnit等框架相比，这确是一个进步，我们在获知测试结果的同时，还依稀中看到了这个特性的需求描述，前提是你要给出一个很好很精确的描述，但这已经不是框架可以帮助你做的了^_^。另外即使你的测试失败了，你甚至可以不通过错误提示中的源码文件名和行号信息也可以快速定位到错误的位置所在。因为错误周围是有足够的上下文信息的。</p>
<p>其次，CBehave支持mock。CBehave中mock的实现完全参考了我之前设计的单元测试框架<a href="http://code.google.com/p/lcut" target="_blank">LCUT</a>，下面是一个简单的例子(片断，完整代码参加cbehave/src/example/product_database_test.c)：</p>
<p>FEATURE(1, &quot;Get the total count of employees&quot;)<br />
	&nbsp;&nbsp;&nbsp; SCENARIO(&quot;Get the total count of employees&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN(&quot;The db connection is ready and there are 5 employees in total&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CBEHAVE_RETV_RETURN(connect_to_database, 0&#215;1234);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CBEHAVE_ARG_RETURN(table_row_count, 5);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CBEHAVE_RETV_RETURN(table_row_count, 0);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN(&quot;We call function: get_total_count_of_employee&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int count = get_total_count_of_employee();<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN(&quot;The total count of employees we read from db should be 5&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SHOULD_INT_EQUAL(count, 5);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN_END</p>
<p>&nbsp;&nbsp;&nbsp; SCENARIO_END<br />
	FEATURE_END</p>
<p>最后，CBeahve还支持多种SHOULD_XX宏，并且可根据需要灵活添加。目前已支持整型、字符串以及布尔类型的判定。</p>
<p>关于CBehave的实现历程这里也简单说一下。</p>
<p>首先需要确定CBehave的&quot;长相&quot;，也就是CBehave采用的行为描述模板是啥样子的。这块儿确是花了我不少的时间，查看各种资料以及研究其他框架的设计，最终选择了Feature+Scenario以及用Given-When-Then来描述行为的&quot;文档模板&quot;。</p>
<p>其次，有了&quot;文档模板&quot;后如何将其转换为可执行的代码实体？这块也费了我不少脑细胞。思前想后，最终设计是将Feature转换成一个可运行的实体-函数。另外我在函数中使用{}来物理划分Scenario，{}可以隔离变量的可见性和作用域，已达到多个Scenarios定义和执行互不干扰的目的。</p>
<p>上面的标准文档结果中的一个FEATURE宏展开后的样子大致是这样的：</p>
<p>static void cbehave_feature_n(void *_state) {<br />
	&nbsp;&nbsp;&nbsp; cbehave_state _old_state;<br />
	&nbsp;&nbsp;&nbsp; cbehave_feature_entry(&#8230;, &#038;_old_state, _state);</p>
<p>&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Scenario #1 */<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int _scenario_state = 0; \<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cbehave_scenario_entry(x, _state);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cbehave_scenario_exit(&#038;_scenario_state, _state);<br />
	&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Scenario #2 */</p>
<p>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; &#8230; &#8230;<br />
	&nbsp;&nbsp;&nbsp; {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /* Scenario #n */</p>
<p>&nbsp;&nbsp;&nbsp; }</p>
<p>_feature_over: \<br />
	&nbsp;&nbsp;&nbsp; cbehave_feature_exit(&#8230;);<br />
	}</p>
<p>关于feature函数的命名，我考虑了很长时间，由于从外界输入的信息无法约束，这里我引入了Feature序号，并同时将序号作为Feature函数名的一部分。例如：<br />
	FEATURE(10, &quot;fopen features&quot;)<br />
	展开后的feature函数名就是cbehave_feature_10，这样做对用户也有一定约束，但约束较小，只需CBehave用户保证各个Feature的序号不同即可。</p>
<p>在使用CBehave时，很可能出现另外一个问题:那就是测试代码在GIVEN或WHEN区段中依赖的一些资源申请或其他代码的初始化可能失败或出现异常。遇到这种情况时，用户多选择return或exit。但一旦用户这样做，CBehave就无法统计和输出测试失败情况或统计的不够准确了。为了尽量保证CBehave统计的精确性，CBehave提供了一个宏FEATURE_RETURN供用户使用。FEATURE_RETURN将控制权转移到Feature函数的末尾，也就是上面宏展开末尾的哪个_feature_over跳转标识符处，这样Feature有机会把这一错误情况记录下来。另外还可以保证其他Feature测试的继续运行。这里举个简单例子(完整代码参见cbehave/src/example/text_editor_test.c)：</p>
<p>FEATURE(1, &quot;Text Editor &#8211; Open Exsited File&quot;)<br />
	&nbsp;&nbsp;&nbsp; SCENARIO(&quot;Open an Exsited File and write something to it&quot;)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN(&quot;A file named foo.txt&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FILE *fp = NULL;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char *buf = &quot;Hello Cbehave!&quot;;<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GIVEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN(&quot;we open the file and write something to it&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fp = fopen(&quot;foo.txt&quot;, &quot;r+&quot;);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!fp)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; FEATURE_RETURN(errno);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; WHEN_END</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN(&quot;We should see [Hello Cbehave] has been written into foo.txt&quot;)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (fp)<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fclose(fp);<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; THEN_END<br />
	&nbsp;&nbsp;&nbsp; SCENARIO_END<br />
	FEATURE_END</p>
<p>例子中如果fopen打开foo.txt失败，代码中调用了FEATURE_RETURN来应对这一情况，而不是直接调用return或exit。</p>
<p>最后，与LCUT不同，Cbehave会尝试运行完所有Feature的测试，而不是遇到测试错误就停止运行。</p>
<p>因为之前有过LCUT<a href="http://http://tonybai.com/2005/11/08/the-design-and-implementation-of-c-unittest-framework/" target="_blank">单元测试框架的设计和实现</a>经验，这次CBehave框架的设计和实现就相对容易了些。CBehave的设计用了一天时间，上周末两天&quot;百忙&quot;中抽空完成了编码和测试，目前已提供了<a href="http://cbehave.googlecode.com/files/cbehave-0.1.0-beta.zip" target="_blank">cbehave-0.1.0-beta</a>版供下载体验，欢迎大家提出你的宝贵意见和建议^_^，更多关于CBehave使用方面的细节请参考CBehave用户指南(<a href="http://code.google.com/p/cbehave/wiki/CBehave_User_Guide_cn">http://code.google.com/p/cbehave/wiki/CBehave_User_Guide_cn</a>)。</p>
<p>BTW，我并不是一个纯粹的TDDers。我个人认为完全采用TDD或是BDD还是有一定局限的。是否采用这些方式进行开发还要视产品(或项目)的时间、质量、人员能力等诸多制约因素而定。个人推断国内的C程序员多普遍缺乏采用框架进行单元测试的意识，BDD或TDD的推广还是任重道远的。</p>
<p style='text-align:left'>&copy; 2011, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2011/08/15/cbehave-a-bdd-framework-for-c/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>行为驱动开发导引</title>
		<link>https://tonybai.com/2011/08/10/introducing-bdd/</link>
		<comments>https://tonybai.com/2011/08/10/introducing-bdd/#comments</comments>
		<pubDate>Wed, 10 Aug 2011 15:07:00 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[BDD]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[C]]></category>
		<category><![CDATA[CBehave]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JBehave]]></category>
		<category><![CDATA[JRuby]]></category>
		<category><![CDATA[JUnit]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Rspec]]></category>
		<category><![CDATA[Ruby]]></category>
		<category><![CDATA[TDD]]></category>
		<category><![CDATA[博客]]></category>
		<category><![CDATA[学习]]></category>
		<category><![CDATA[程序员]]></category>
		<category><![CDATA[编码]]></category>
		<category><![CDATA[翻译]]></category>
		<category><![CDATA[行为驱动开发]]></category>

		<guid isPermaLink="false">http://tonybai.com/2011/08/10/%e8%a1%8c%e4%b8%ba%e9%a9%b1%e5%8a%a8%e5%bc%80%e5%8f%91%e5%af%bc%e5%bc%95/</guid>
		<description><![CDATA[<p>本文翻译自Dan North的文章&#34;Introducing BDD&#34;。<br />
<br />
我遇到了一个问题。当我在不同环境的多个项目中使用和教授类似测试驱动开发(test-driven development, TDD)这样的敏捷实践时，我总是能遇到来自程序员们相同的困惑和误解。他们想知道从哪里开始、测什么不...</p>]]></description>
			<content:encoded><![CDATA[<p>本文翻译自<a href="http://dannorth.net" target="_blank">Dan North</a>的文章&quot;<a href="http://dannorth.net/introducing-bdd/" target="_blank">Introducing BDD</a>&quot;。</p>
<p>我遇到了一个问题。当我在不同环境的多个项目中使用和教授类似测试驱动开发(test-driven development, <a href="http://en.wikipedia.org/wiki/Test-driven_development" target="_blank">TDD</a>)这样的敏捷实践时，我总是能遇到来自程序员们相同的困惑和误解。他们想知道从哪里开始、测什么不测什么、一次测试多少、谁来调用他们的测试以及如何理解为什么一个测试失败了。</p>
<p>越是深入TDD，我越能感觉到我对TDD认知过程是时断时续、逐步掌握的，还远未进入到死胡同。我记得多数时间我想到的都是&quot;这只是别人告诉我这样做的&quot;，而不是&quot;哇，我明白为何要这样做了&quot;。我断定一定可以通过某种方法将TDD直截了当地呈现给那些优秀的程序员们，并且可以避免所有陷阱。</p>
<p>我给出的答案是<a href="http://en.wikipedia.org/wiki/Behavior_Driven_Development" target="_blank">行为驱动开发</a>（Behaviour-drive Development, BDD)。它从已有的敏捷实践演化而成，其设计目的是让敏捷实践对于采用敏捷软件交付的新团队来说变得更加容易理解和高效。随着时间推移，BDD已经发展为一种包含敏捷分析以及自动验收测试的敏捷实践。</p>
<p><strong>测试方法名应该成句</strong></p>
<p>我第一次发出&quot;Aha!&quot;是当看到我的同事Chris Stevenson开发的一款看似简单的名为<a href="http://agiledox.sourceforge.net/" target="_blank">agiledox</a>的工具程序时。这个程序用于处理<a href="http://www.junit.org/" target="_blank">JUnit</a>的测试类，并以普通句子的形式打印出方法名。其中的一个测试用例看起来像这样：</p>
<p>public class CustomerLookupTest extends TestCase {<br />
	&nbsp;&nbsp;&nbsp; testFindsCustomerById() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; testFailsForDuplicateCustomers() {<br />
	&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	&nbsp;&nbsp;&nbsp; &#8230;<br />
	}</p>
<p>结果是这样的：</p>
<p>CustomerLookup<br />
	- finds customer by id<br />
	- fails for duplicate customers<br />
	- &#8230;</p>
<p>&quot;test&quot;这个词被从类名和方法名中剥离出来，采用驼峰式命名方式(<a href="http://en.wikipedia.org/wiki/CamelCase" target="_blank">camel-case</a>)的方法名被转换为普通的文本。这就是这个工具所做的一切，但是它产生的效果却是惊人的。</p>
<p>开发人员发现这至少可以为他们产生一些文档，所以他们开始编写使用真实句子作为名字的测试方法。更重要的是，当他们使用业务领域的语言作为方法名后，生成的文档对于商业用户、分析师以及测试人员变得同样有意义了。</p>
<p><strong>一个让你专注于测试方法的简单句子模板</strong></p>
<p>接下来我无意中发现了以单词&quot;should&quot;作为开头的测试方法命名手法。这个句子模板-&quot;<strong><em>这个类应该(should)做某事</em></strong></p>
<p>	&quot;-意思是你只能为当前类定义测试，这会让你保持专注。如果你发现自己编写了一个名字不符合该模板的测试，这表明这个行为很可能属于其他地方。</p>
<p>例如，我正在编写一个用于校验屏幕输入的类。大多字段都是常规的客户信息-名，姓氏等等，不过其中有一个字段用于输入出生日期，还有一个字段用来输入年龄信息。我开始编写一个ClientDetailsValidatorTest类，其中包含诸如testShouldFailForMissingSurname和testShouldFailForMissingTitle的测试方法.</p>
<p>接下来，我开始着手计算年龄，我的思维进入了一个充斥着繁琐业务规则的世界：如果客户同时提供的年龄和出生日期信息两者无法匹配该怎么处理？如果提供的出生日期是今天呢，又该如何处理？如果我只得到了出生日期，我应该如何计算年龄呢？为了描述这个行为，我正在编写的一些测试方法名字日益复杂，所以我考虑将其交给其他类去处理。这促使我引入了一个名为AgeCalculator的新类以及对应的AgeCalculatorTest。所有有关年龄计算的行为都放到calculator这个类中，这样validator类只需要一个有关年龄计算的测试例，并保证其与calculator类可以正确地交互。</p>
<p>如果一个类做了不止一件事情，这对我而言通常是一个提示：我应该引入其他类来分担一些工作了。我会将该新服务定义成一个可以<em>描述它自身职责</em><br />
	的接口，并且将该服务通过类的构造函数传入：</p>
<p>public class ClientDetailsValidator {<br />
	&nbsp;<br />
	&nbsp;&nbsp;&nbsp; private final AgeCalculator ageCalc;<br />
	&nbsp;<br />
	&nbsp;&nbsp;&nbsp; public ClientDetailsValidator(AgeCalculator ageCalc) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.ageCalc = ageCalc;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</p>
<p>这种将众多对象连接在一起的手法，即通常所说的依赖注入(dependency injection)，在与<a href="http://tonybai.com/2008/04/12/mock-test-in-c-unit-test/" target="_blank">mock</a>机制一同使用时特别有用。</p>
<p><strong>当测试失败时，一个表达良好的测试名字十分有用</strong></p>
<p>不久，我就发现如果我修改代码后导致测试失败，我可以查看测试方法的名字并识别出这段代码预期的行为。通常发生的情况有下面三种：</p>
<p>* 我引入一个bug。都怪我。解决方法：修正这个bug。<br />
	* 预期行为仍然有意义，但已移至别处了。解决方法：将这个测试例移走，也许还要进行一些修改。<br />
	* 这个行为已经不再正确 &#8211; 系统的前提发生了改变。解决方法：删除这个测试。</p>
<p>在敏捷项目中随着你的理解的深入，最后一种情况很可能发生。不幸的是，TDD新手对删除测试例有着与生俱来的恐惧，就好像这样做会降低他们的代码质量似的。</p>
<p>在一个更微妙的方面上，与那些更加正式的单词will或shall相比，单词should的含义更加显而易见。Should隐式地允许你挑战测试例的前提：&quot;它应该吗？果真是这样吗&quot;。这样我们可以更加容易判断出测试失败到底是由于你引入的一个bug还是只是因为你之前对系统行为的假设已经不再正确。</p>
<p><strong>与&quot;测试(test)&quot;相比，&quot;行为(Behaviour)&quot;是个更加有用的词</strong></p>
<p>现在，我有一个工具 &#8211; agiledox &#8211; 用来删除单词&quot;test&quot;，并且我拥有一个编写测试方法名的模板。我突然意识到人们关于TDD的误解几乎都归结到单词&quot;test&quot;上。</p>
<p>这并不是说测试不是TDD所固有的 -测试方法的结果集是一个保证你的代码可以正确工作的有效途径。但是，如果方法没有全面地描述你的系统的行为，那么它们会使你产生一种虚假的安全感。</p>
<p>在进行TDD时，我开始使用单词&quot;behaviour&quot;代替&quot;test&quot;。我发现这样做不仅合适，而且之前在TDD辅导时遇到的各类问题也都迎任而解。现在我已经有了这些问题的答案。什么来调用你的测试，这个问题变得很容易回答 &#8211; 我们在一个句子中调用你的测试，这个句子描述了你感兴趣的行为。测试多少用例才算充分 &#8211; 这取决于你在一个句子中可以描述多少行为。当测试失败时，我们可以简单地按照上面描述的过程解决- 要么你引入了一个bug，要么这个行为被移走了，或者是这个测试不再有意义了。</p>
<p>我发现这种从考虑测试到考虑行为的思维转变影响是如此巨大，以致于我开始把TDD称作为BDD，或行为驱动开发了。</p>
<p><strong>与测试相比，JBehave更强调行为</strong></p>
<p>在2003年末，我觉得是时候采取实际行动了。我开始编写一个名为JBehave的JUnit的替代品，它删除了代码中所有涉及测试的词汇，并替换为与验证行为相关的词汇。我这样做的目的就是为了看看如果我严格坚持我的行为驱动方法，这样一个框架将会如何演变。同时，我认为这也是一个有价值的教学工具，可以用来介绍TDD和BDD，同时可以避免大家分心于那些Test相关的词汇。</p>
<p>为了定义一个假想CustomerLookup类的行为，我编写了一个行为类，例如，CustomerLookupBehaviour。这个类包含以单词&quot;should&quot;开始的方法。行为运行器(behaviour runner)将实例化这个行为类，并依次调用每个行为方法，就像JUnit处理测试例的方式一样。它还会报告执行进度并在结束时输出一份总结。</p>
<p>我的第一个里程碑是使得JBehave做到自我验证。我只不过增加了一些行为，使得JBehave可以验证自己。我能够将所有JUnit测试例移植为JBehave行为并且可以像JUnit那样立即获取验证结果的反馈。</p>
<p><strong>确定最重要的行为</strong></p>
<p>接下来，我发现了商业价值的概念。当然了，我一直知道我编写软件的原因，但是我从未真正考虑过我现在所编写代码的价值。我的另外一个同事，业务分析师Chris Matts，促使我开始考虑在行为驱动开发背景下的商业价值。</p>
<p>假定我在头脑中已经有了使JBehave自托管的目标，我发现一个真正有用的保持专注的方法就是问：<strong>系统<em>尚未</em><br />
	实现的最重要的特性是什么</strong><br />
	？</p>
<p>这个问题需要你能识别出你尚未实现的特性的价值，并按优先级顺序对它们进行排序。它也可以帮助你制定这个行为方法的名字：系统尚未实现X（X是一个有意义的行为)，X是重要的，这意味着系统应该实现X；所以你的下一个行为方法很简单：</p>
<p>public void shouldDoX() {<br />
	&nbsp;&nbsp;&nbsp; // &#8230;<br />
	}</p>
<p>现在我有了另外一个TDD问题即&quot;从何开始&quot;的答案了。</p>
<p><strong>需求也是行为</strong></p>
<p>此时此刻，我拥有了一个框架，它可以帮助我理解，并且更重要的是解释TDD是如何工作的，并且还可以帮助我解释一种避免我遇到的所有陷阱的方法。</p>
<p>临近2004年年底，当我向Matts描述我新发现的、基于行为的词汇时，他说&quot;但是这很像分析&quot;。当我们讨论到这些时，我们停顿了很长时间，然后我们决定将这种行为驱动的思维方式应用于定义需求。如果我们可以为分析师、测试人员、开发人员以及业务开发出一致的词汇，那么我们就可以很好的消除技术人员和业务人员沟通过程中产生的一些岐义和错误传达。</p>
<p><strong>BDD为分析提供了一种&quot;通用语言(ubiquitous language)&quot;</strong></p>
<p>就在此期间，Eric Evans出版了他的畅销书《<a href="http://book.douban.com/subject/1629512/" target="_blank">领域驱动设计</a>》。在书中，他使用一种基于业务领域的通用语言描述了系统建模的概念，这使得商业词汇渗透到了代码库中。</p>
<p>Chris和我意识到我们正试图为<em>分析过程本身</em><br />
	定义一种通用语言！我们拥有一个很好的起点。公司内部已经有了一个常用的故事模板，看起来类似这样：</p>
<p><strong>作为(As a)</strong><br />
	[X]<br />
	<strong>我要(I want)</strong><br />
	[Y]<br />
	<strong>结果是(so that)</strong><br />
	[Z]</p>
<p>这里Y是某个特性，Z是这个特性的价值或带来的益处，X是这个特性的受益人或角色。它的优点在于当你第一次定义需求故事时，它将迫使你识别交付这个故事的价值。当一个故事没有真正的商业价值，它常常可以归结为类似：&quot;&#8230;我想要[某个特性]，所以[我就去做，好吗?]&quot;。这样可以更加容易地消减一些难懂的需求。</p>
<p>从这点触发，Matts和我开始着手了解每个敏捷测试人员已经知道了些什么：一个故事的行为仅仅是其验收标准 &#8211; 如果系统满足所有验收标准，它的行为就是正确的；相反，它的行为就是不正确的。所以我们创建了一个模板来捕捉一个故事的验收标准。</p>
<p>这个模板应该足够宽松，这样分析师们不会感觉到矫揉造作或受到约束。不过它也应该足够结构化，这样我们可以将故事分解成组成片断并自动生成它们。我们从场景(scenarios)的角度来描述验收标准，采用如下形式：</p>
<p><strong>假定(Given)</strong><br />
	一些初始上下文，<br />
	<strong>当(When)</strong><br />
	一个事件发生，<br />
	<strong>那么(then)</strong><br />
	要保证一些结果。</p>
<p>为了说明这一点，我们使用ATM机这个经典的例子。其中的一个故事卡可能看起来像这样：</p>
<p>+标题: 客户取现金+<br />
	作为(As)一个客户<br />
	我想(I want)从一台ATM机中取现金<br />
	结果(so that)是我不需要在银行中排队等候</p>
<p>那么我们怎么知道何时我们已经交付了这个故事呢？这里有几种场景要考虑：账户可能有盈余，账户可能被透支，但在透支额度以内，账户可能被透支且超出透支额度。当然，还有其他一些场景，注入如果账户有盈余，但是这次取款将使得账户透支，或如果自动取款机现金量不足。</p>
<p>使用given-when-then模板，头两个场景可能看起来是这样的：</p>
<p>+场景 1: 账户有盈余 +<br />
	假定账户有盈余<br />
	并且(And)卡片是有效的<br />
	并且取款机有现金<br />
	当(When)客户请求现金时<br />
	那么(Then)要保证这个账户被记入了贷方<br />
	并且(And)保证现金被取出<br />
	并且保证卡片被返还</p>
<p>注意，and用于以自然的方式连接多个givens(假定)或多个outcomes(结果)。</p>
<p>+场景 2: 账户透支超出额度限制+<br />
	假定账户被透支<br />
	并且卡片是有效的<br />
	当客户请求现金<br />
	那么要保证显示一条拒绝消息<br />
	并且保证现金没有被取出<br />
	并且保证卡片被返回</p>
<p>两个场景都是基于同样的events(事件)，甚至有一些共同的givens和outcomes。我们要通过重用givens，events和outcomes充分利用这一点。</p>
<p><strong>验收标准应该是可执行的</strong></p>
<p>场景的片断-givens，events和outcomes-的粒度足够细，可以直接用代码表示。JBehave定义了一个对象模型，该模型允许我们直接将场景片断映射为Java类。</p>
<p>你编写一个类用于代表每个given：</p>
<p>public class AccountIsInCredit implements Given {<br />
	&nbsp;&nbsp;&nbsp; public void setup(World world) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}<br />
	public class CardIsValid implements Given {<br />
	&nbsp;&nbsp;&nbsp; public void setup(World world) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</p>
<p>并且另外一个用于代表event：</p>
<p>public class CustomerRequestsCash implements Event {<br />
	&nbsp;&nbsp;&nbsp; public void occurIn(World world) {<br />
	&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8230;<br />
	&nbsp;&nbsp;&nbsp; }<br />
	}</p>
<p>outcomes也是这样。然后JBehave将所有这些联系起来并且执行它们。它创造了一个&quot;世界&quot;，只是用于存储你的对象，它将这个世界依次传递给每个givens，这样这些givens就可以用已知状态生存于这个世界中了。JBehave接下来告诉events出现在这个世界，它们实现了场景的实际行为。最后，它将控制权传递给我们为这个故事定义的任一一个outcome。</p>
<p>用一个类来表示每个片断使得我们可以在其他场景或故事中重用这些片断。起初，我们通过使用mock机制设置账户有盈余或者卡片有效来实现片断。这些形成了实现行为的起始点。当你实现应用时，你修改givens和outcome，使用你实现的实际类，这样直到场景完成为止，他们已经成为正确的端到端的功能测试。</p>
<p><strong>BDD的现在和未来</strong></p>
<p>经过一次简短的停顿后，JBahave回归到积极的开发。其核心已经相当完整和健壮了。下一步是将其与流行的Java IDE如IntelliJ IDEA和Eclipse集成在一起。</p>
<p><a href="http://daveastels.com/" target="_blank">Dave Astels</a>一直在积极推动BDD。他的博客以及各类发表的文章引发了一系列活动，最引人注目的是<a href="http://rspec.info/" target="_blank">rspec</a>项目，它是一个用Ruby语言实现的BDD框架。我已经开始开发rbehave，它将是一个用Ruby实现的JBehave。</p>
<p>我的许多同事都一直在现实世界中的各种项目中使用了BDD技术，并且发现这个技术非常成功。<a href="http://jbehave.org/" target="_blank">JBehave</a>的故事runner &#8211; 校验验收标准的部分 &#8211; 正在积极的开发中。</p>
<p>我们的目标是拥有一个往返的编辑器，这样业务分析师和测试人员可以在一个普通的文本编辑器中捕获故事，同时这个编辑器还可以为行为类生成桩代码，所有这些都使用业务领域的语言描述。BDD的演化是与大家的帮助分不开的，我在这里十分感谢他们。</p>
<p style='text-align:left'>&copy; 2011, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2011/08/10/introducing-bdd/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
