Archive

Monthly Archives: September 2009

想年轻时,自动化一切并乐此不疲,直到我碰见了Cruise的部署问题。

大家都知道,Cruise采取的是Smart Server + Dummy Agent的架构,每个Server可以对应N个Agent, 开发是一件乐事,然而部署不是。 每次发布,大家都得小心翼翼的

ssh到Server上用apt-get升级,

根据环境下载.exe, osx.zip或者.deb

scp安装包到相应的agent

ssh到每一台agent,升级agent

嘿,有事情不对劲儿了,不是么? 你看什么了? 手工过程,很多邪恶的手工过程,自动化才是王道!

但是,请等一下…..

为什么要去手工下载安装包更新Agent?
因为安装文件是按照不同平台打包的,放在独立的下载站点上,由于Agent可能访问不了外网,它无法自行去下载站点下载安装文件。

既然Cruise Agent知道Cruise Server在哪儿,为什么不把Cruise Agent的安装文件放在Cruise Server上?
你是说我们要把所有的Agent安装包放在Cruise Server上? 这样占太多空间了。

为什么不能只提供一个jar包呢? 它是Java应用,只需要一个jar,不是么?
嗯….??!!

为什么需要脚本重启它?
Agent是一个Java进程,它不能把自己杀死再重启。所以必须有另外的脚本安装更新后再重启。



原来问题的关键不是自动化部署,而是:

1. Agent得不到新版本的安装包。

解决方案: agent.jar放在Server上,用MD5一比就知道有没有更新了

2. Agent无法把自动重启。

解决方案:写一个很薄的bootstrapper层,用它启动/重启agent

搞定,我们作了什么? 我们并没有解决部署问题,我们解决了一个架构问题,或者说是设计问题,而部署问题不治自愈。

我相信这是Kent在Responsive Design Worhshop试图表达的观点之一: 设计者决不能停留在问题的表面,在带上技术专家的帽子激情满怀的解决问题之前,停下来,问5次为什么? 不要被幻象蒙蔽了眼睛。因为

这也许不是配置问题,而是设计问题
这也许不是性能问题,而是设计问题
这也许不是部署问题,而是设计问题
….

米高陈很久以前曾经跟我说过,作为一个专业的blogger一定要有自己的域名,作为一名怕麻烦的实用主义者兼谷粉,我对此相当的不以为然,然而人算不如天算,bl0gger.c0m被结结实实的封住了,之所以用结结实实这个词,是因为每年总有那么几天,不过年,不过节,不Five.4, 不Six.4, 不Ten.1, 不…… 的时候,bl0gger.c0m还是可以访问的,这次彻底的封了,断了我的念想,痛定思痛,自力更生,注册了域名,架了服务器重出江湖。

我的blog架在http://iamhukai.com, 感兴趣的同学也可以用RSS订阅这个网址。

另一个域名http://iamhukai.info被用来架个人名片。效果如下,大家也可以帮着点点,看看javascript效果有没有bug:

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

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一样,给典型数据用大家都明白的词汇起个名字,会让测试数据不管是读起来还是用起来都简单很多。