2014年3月25日星期二

Mock-based Unit testing revisit

The risk of using mock in Unit testing is that if you mock some behavior of an object, that assumption could easily break as the codebase keep evolving. If you forget to update those mocked setups when things change(this is always happening), you'll find your tests in a situation that it doesn't reflect production usage anymore.

Then what is the value of a test which doesn't reflect real production scenario?

2014年3月24日星期一

Timezone change confuses Jenkins

Today we encountered a mess on Jenkins.

The significant issues were:

  • Jenkins scheduled multiple builds of the same job in parallel which affected each other and failed quickly
  • Newly scheduled builds didn't appear in Build History, as well as the Build Monitor screen on our wall projection

It's actually weird that although Build History doesn't update, when you go to Build Trend you can find them, which implies some underlying inconsistency in Jenkins' implementation.

Finally it's proven that it's caused by timezone change: from Australia/Melbourne to UTC, which confused Jenkins because last build happened in the future!

This apparently also affected Jenkins' change polling mechanism, which made it detected new changes everytime.

2014年3月23日星期日

Argument capture in Mockito

Interestingly, the way that Mockito captures argument is afterwards! So you can basically write code like this:

   mock.doSomething(actualArgument);

   ArgumentCaptor argument = ArgumentCaptor.forClass(Person.class);
   verify(mock).doSomething(argument.capture());
   assertEquals("John", argument.getValue().getName());

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的表达式即自动删除)