2010年6月16日 星期三

Junit Theory runner的另一种实现

最近做的一个测试项目,测试报告是交付物的一部分。

我们用了junit来实现并组织我们的测试用例。之前困扰我们的一个问题是:大量测试用例只是测试数据和用例名称不同,但没有合适的数据驱动测试工具能让我们做到既省代码又得到良好的测试报告。

我们尝试了Junit4的Parameterized runner和Theory runner,做数据驱动测试都没问题,可就是生成的报告不尽如人意。

我对测试报告的需求是:针对每项测试数据生成一个测试方法,同时有能力根据测试数据定制测试方法名。

研究了半天Theory的源码,终于找到了一个简单的解决方法。

结果分享在这里:http://code.google.com/p/mytheories/

2009年3月24日 星期二

把条件判断提到方法外

我在开发中见过这样的方法:
function doSomething(flag, arg1, arg2) {
if !flag return;
// doSomething logic
....
}

那我就奇怪了,既然caller了解flag,为何不先判断,符合条件才进入doSomething方法呢?

你可能会说我不会写出这样的代码。那看看下面这种稍复杂点儿的变体:
function doSomething(arg1, arg2) {
flag = arg1.checkSomething(arg2);
if !flag return;
// doSomething logic
...
}

根据传入参数的计算结果来决定是否执行后续逻辑的情况与第一个例子其实是一样的。

有时候出现这种情况可能是代码在修改的过程中没有意识到这个问题。另外一种可能的理由是doSomething被多处地方调用,为了避免重复条件判断逻辑,所以把条件判断下推到doSomething方法。

这个理由在某些情况下可能是合理的,比如调用地方太多,改动起来会影响到很多测试,而当前没有足够时间。

但一般来说,我认为对这样的情况,减少条件判断的重复的好处不足以弥补代码在表达性上的损失,而且还要加上doSomething方法的测试所增加的复杂度的代价。

2009年2月26日 星期四

切换hg分支

用hg已有多日,下面的这个法子是前段时间摸索出来的偷懒招数。

问题是这样:项目中有两个代码库分支:trunk和1.2。每个代码库都超过500M。我的C盘剩余空间已经不多,没法同时保留两个分支的本地副本。平时我主要在trunk上工作,有时要修1.2的bug就得把分支切换到1.2上。重新checkout整个代码库很慢,怎么办呢?

既然是分支,那它们肯定在某一点之前共享相同的历史。所以我只需要先回溯到这个基点,再获取1.2分支上自从这一点后所有的历史即可!

具体做法:

1. 执行hg out ${url_of_1.2},得到trunk分支上与1.2不同的最早的节点,记为revision1
2. 执行hg strip revision1,删除以revision1为根的所有版本
3. 重复步骤1,检查是否还有1.2分支上不存在的版本;如有,则重复步骤3
4. 至此,本地副本上的所有版本都是1.2上的版本;这时可以执行hg pull ${url_of_1.2}获取1.2上的新的版本
5. 大功告成,你已经得到一个与中心库上1.2分支完全一致的代码库

2009年2月11日 星期三

Inline parameter

Derek同学的启发,我想把我们在实践中总结下来的一些重构小技巧记录下来。

我们自己在实际中经常用到Extract Parameter这个重构功能,感觉非常方便。但可惜的是没有这个功能的反向功能。比如说一个方法接受三个参数,但检查这个方法所有的调用处发现,第三个参数完全可以由前两个参数(或者其他可视范围内的值)计算得出。这时你就需要一种重构来把这个参数的计算过程推到方法内部,然后删除这个参数。

遗憾的是IntelliJ不提供这样的自动化重构功能。我们可以手动来做,但如果代码很多,就有风险。怎么尽量降低这种风险呢?

我想到了一种方法。假设目标方法为foo,我们想要删除的参数为bar。步骤如下:
1. 对计算bar的表达式提取方法(IntelliJ能自动检测到相同代码片段并提示自动替换)
2. 在方法foo的所有调用处,把参数bar替换为对新方法的调用
3. 修改foo方法的代码,在第一行将bar赋值为对新方法调用的返回值
3. (在foo方法内,)内联bar(,然后bar在foo方法内部已没有引用)
4. 修改foo方法的签名,安全删除bar(所有调用处用于生成bar的表达式即自动删除)

2009年1月22日 星期四

Fix Google Reader显示问题

早就觉得Google Reader的段落排版有问题了。字体太小,排版紧凑,对眼睛很不友好!

以往每次看文章,都得敲两次=,放大字体,但还是解决不了间距的问题。密密麻麻的,看着不舒服。

今天终于用firefox的user css解决了这个问题。

只需把下面这段代码放到firefox的profile\chrome下的userContent.css里:

@-moz-document url-prefix(http://www.google.com/reader/view/) {
.entry-body { font-size: 1.4em; line-height: 1.5em; }
}

Firefox3的profile目录在Windows下是:%APPDATA%\Mozilla\Firefox\Profiles\xxx.default

参考:Site specific stylesheet in Mozilla

2009年1月3日 星期六

Parallelize your tests with Test-Load-Balancer

With the fact that Cruise runs jobs in parallel to make long running test suites go faster, you can simply split your long running test suite into multiple suites and create jobs for each one. Given enough build agents, Cruise can run all jobs within a specified stage simultaneously, which means your long running testing can be done in a snap.

But wait, why do I need to manually split my test suites?

For example, I have a unit-test target in my ant build script which include more than 100 tests. Those tests are organized by corresponding packages, so it's hard to split them in an easy way. It's a burden to most of Cruise users.

That's why we have Test-Load-Balancer now.

What is test-load-balancer?

TLB(Test-Load-Balancer) is a tool that can split test suites for you in Cruise. With TLB, you can simply specify how many pieces you want to split for a specific job using an easy, intuitive way. TLB can automatically work out how many pieces and which piece that current job should do. For example, you have a unit test job running on Linux environment named 'ut-linux', it has 100 tests and costs 10 minutes. Now you want to split it to three jobs, what you need to do is just replace the old 'ut-linux' job with three jobs: 'ut-linux-1', 'ut-linux-2', 'ut-linux-3'. Then you can run unit tests on Linux in three pieces simultaneously, and get benefits of reducing time cost to one third of the old cost.

How to use it?

First, download test-load-balancer jar file and add it to the classpath of ant build script.

Then, change your build.xml as following:

<typedef name="filter-fileset" classname="com.googlecode.tlb.support.junit.FilterFileSet" classpathref="classpath"/>

<junit>
<batchtest todir="target/test-results">
<filter-fileset dir="target/test-classes" includes="**/*Test.class"/>
</batchtest>
<classpath refid="classpath" />
</junit>

Third, change your Cruise configuration to use TLB style jobs such as ut-linux-1, ut-linux-2, ut-linux-3 ...

Then you can get the benefits of faster run.

Further information about test-load-balancer.

A video demonstration about TLB:

2008年12月28日 星期日

我的TDD实践总结

加入TW一年多,也是实践了TDD一年多。一开始是跟着别人照猫画虎,最近开始总结出来点意思了。

这个转变是我们组的Tech Lead——Chris Stevenson给我带来的。

跟Chris一起pair时,一个印象深刻的事情是他写代码,如果发现需要的东西还不存在,比如需要某个对象提供某个方法,甚至需要一个新的对象封装某些逻辑,他是直接敲出这样的方法,完成当前需要的逻辑,然后借助IDE的自动修正功能生成所需的方法。

这种编写代码的过程,就好像是一个类的方法,都是由它的客户们的需求驱动出来的。而且因为方法名都是在编写客户代码时编写出来的,那些名字在客户代码环境中看起来非常贴切,可读性非常好。

对比起我之前的编码习惯,可以发现一些有意思的结论。我之前开始写程序时,会先想出一个我需要的类,然后基于这个类的数据成员构思出它应该对外提供的方法接口。有时候甚至会反复斟酌很久,以图设计出一个看上去“完美”的接口。其实最后那些精心设计出来的方法,有多少是集成后保留下来的(或者经过了某些修改),仔细想想其实并不乐观。

而Chris的这种习惯,基本上一个类的接口,都是在实际需要时才被创建出来,这样做出的类的接口非常紧凑,而且更贴近需求。

看到这里,熟悉TDD的人应该已经看出来了,其实这就是TDD的思想——从需求出发,思考一个类封装什么样的职责,需要提供什么样的接口。

现在,我正实践着这种编程方式,而且正从中获益。