Posts Tagged ‘单元测试’

Persona测试数据

Saturday, September 5th, 2009

最早的时候,动物星球项目的测试数据本来没有组织,因为太混乱,后来有智者添加了

createPanda(),

createHabitat()

createXxx

等方法,总算给测试数据们找到了组织,再到后来,系统变的复杂了,动物和它们的栖息地的类型正变的越来越多,先是有人随手写下了:

createPandaLivesInUSA()

不巧,下一个看到这个方法的人正要使用栖息在四川卧龙的熊猫作为用例,没关系,再加一个方法好了,

createPandaLivesInChina()

再后来,有人要用到栖息在美国的大猩猩,没办法

createGorillaLivesInUSA()

紧接着,基于相似的原因,出现了:

createMonkeyLivesInJapan()

你看到什么了? 是的,情况慢慢变的有些失控了。

James (Jim) Barritt写过一篇文章,打了个很好的比喻,上中学的时候大家想必学过因式分解,数字12,我们可以分解为3和4,如果对4再分解,我们们可以得到2和2, 这样我们就有了{2,2,3,4,12}这五种选择,而且更妙的是通过组合我们可以得到 6(2 × 3)。

看来,之前的用例也许粒度太大了,小粒度会鼓励我们通过组合重用功能。如果我们的方法是这样的:

animalLivesInUSA()

animalLivesInChina()

animalLivesInJapan()

createGorilla()

createMokey()

createPanda()

这6个方法不仅满足之前的需求,还可以组合起来用于测试栖息在中国的猴子或者栖息在日本的猩猩:

createMokey()

animalLivesInChina()

或者:

createGorilla()

animalLivesInJapan()

自从找到了秘诀,事情变的容易多了,时间一天天过去,生态环境变的更复杂了,有一天你看到了这样的测试数据:

createPanda()

matureAnimal()

heavyWeight()

animalLivesInUSA()

animalAcrobat()

…..

抓破了脑袋你也不知道一个怎样数据被创建出来了,感谢Pair Programming

嗨,哥们,你写的么?为什么这么长,这么复杂?

是的,你知道的,我们得组合使用这些方法,所有这些方法了我们都有,我只是把它们组合在一起。

可它究竟是什么?

这不是很明白么,一个熊猫,成年的,非常肥胖的熊猫,它栖息在美国,而且它还会耍杂技。

你是说就功夫熊猫那样

哈,没错

看,你们得到了一个Persona的数据:功夫熊猫,一个非常复杂,但是每个人都明白的数据。如果你想测试栖息在美国的成年动物而不关系它是不是熊猫,又或者你想测试会耍杂技的非常肥胖的动物,而不关心它是不是在美国,你都可以用到这个已经完成了的:

createGongFuPanda()

分解过的测试数据便于组合,这是它的优点,但随着被测系统的不断复杂化,它的劣势会变得突出起来,就是即不便于交流,也会产生很多重复的代码,譬如准备一个栖息在美国的野生熊猫(不会杂技)的数据准备步骤可能是这样的。

createPanda()

matureAnimal()

heavyWeight()

animalLivesInUSA()

看到了? 测试数据粒度过细会在不同的测试用例之间引起多少重复!

这时候我们需要适当的聚合它们,并其一个全团队都明白的名字。比如:

createPandaPingping():

(注,平平是1974年中国送给美国的一只熊猫)

对于会杂技的熊猫的测试准备过程变为:

createPandaPingping():

animalAcrobat():

在Cruise团队中,我们用Mingle来代表使用git作为版本管理服务器,rake作为构建工具的复杂Pipeline配置,而Trainlines代表svn作为版本管理服务器,nant最为构建工具,有非常多agent的测试数据。

很多情况下,测试数据之所以作的不是很理想,一是因为写测试代码的随意性太强,经常是随手写下,又被别人随手复制并稍作修改,而且它又属于测试代码,天生的2等公民,基本得不到重构。二是忽视了数据本身用于交流的用途。

所以一是要重构,经常作,流水不腐嘛,不重构它就会发烂。二是像我们给用户作Persona一样,给典型数据用大家都明白的词汇起个名字,会让测试数据不管是读起来还是用起来都简单很多。

如何正确的使用Mock

Friday, December 19th, 2008

首先我不是反Mock者,但确实对使用Mock持比较审慎的态度,因为Mock是非常难于正确使用的, mock最常见的问题在于假设!假设!假设!

有这样一个功能,当有工作的时候,公民需要买需要买医疗保险,住房公积金和养老保险,如果失业了他只需要买养老保险:

public void requreInsurance(Insurances insurances) { if (people.getJob() == null) { insurances.add(new RetirementInsurance()); } else { insurances.add(new HealthInsurance()); insurances.add(new RetirementInsurance()); insurances.add(new HouseFund()); insurances.add(new UnemploymentInsurance()); } }

相应的Mock测试有两种情况需要覆盖:

* mock people对象, 假设getJob方法返回null,验证insurances中只有包括养老保险
* mock people对象, 假设getJob方法返回Not null,验证insurances中包含四金

在这里的Mock测试进行了假设,它的coorelation people对象在有工作的时候返回非Null的Job对象,而在没有工作的时候返回Null,类似的代码在任何一个项目中都可以找得到踪迹。

问题在于这样的假设可以被悄无声息的破坏掉,假如有人重构了People对象, 在没有工作时返回一个new NullJob()对象(Null Object Pattern), 这样重构后,失业的人也不得不买四金了,然而我们之前编写的mock测试会100%的通过

如何解决这样的问题呢?

功能测试是一个解决思路,因为在Mock测试中,我们不断的在层与层之间做出假设,一定需要一个端到端的测试来验证我们的假设是否正确。功能测试解决了部分的问题, 回顾上面的问题,你发现至少需要编写两个功能测试才能百分之百的发现刚才重构引入的bug, 如果只编写了happen path的功能测试(有People工作的功能测试),那你只有祈祷QA能帮你及时的找到问题了。 不幸的是,由于功能测试的代价比较大,所以大多数的人都只会编写有限的功能测试,往往这些测试仅仅用于覆盖Happy path. 对于如此简单的问题功能测试尚且不能解决问题,更遑论我们“大型企业级超复杂”的项目呢。

另一个方法就是减少假设,回顾一下我们的实现代码:

if (people.getJob() == null) { .... } else { ..... }

getJob的返回值是我们需要进行两次假设的根源,如果没有返回会怎样?

public void requreInsurance(Insurances insurances) { people.requreInsurance(insurances); } public clas People { public void requreInsurance(Insurances insurances) { job.requreInsurance(insurances); } } public class Job { public void requreInsurance(Insurances insurances) { insurances.add(new HealthInsurance()); insurances.add(new RetirementInsurance()); insurances.add(new HouseFund()); insurances.add(new UnemploymentInsurance()); } } public class NullJob { public void requreInsurance(Insurances insurances) { insurances.add(new RetirementInsurance()); } }

在第一个例子中我们需要进行两个假设(Null 和 Not Null),而第二个例子,我们只需要一种假设

第一个例子中稀松平常的代码违反了基本的面向对象的原则“封装”,简而言之是tell do not ask!!原则,因为违反了这个原则,我们不得不做出很多假设,减少假设的一个有效途径就是减少return,tell你的对象替你效劳,如果你对写不出健壮的mock测试烦恼,不妨看看是否写出了符合面向对象原则的产品代码,

正确使用Mock的原则就是尽量不用,改善设计才是王道,代码难于使用通常的方法进行测试而不得不使用mock,绝对是一种smell,不要使用mock来掩盖这种味道。

最后推荐李晓同学的

不要把Mock当作你的设计利器

还有感谢Chris Stevenson今天的Session和帮助。