在Mac下启动多个Firefox实例方便JsUnit运行

项目中的JsUnit是使用ant脚本运行的,里面需要设置BROWSER_PATH的环境变量来启动浏览器。在本地check in代码的时候,我们会运行一下测试来减少愚蠢错误被提交到代码控制系统。但是在我的mac下Firefox只能启动一个实例,在运行JsUnit test的时候会提醒我已经打开了Firefox,不能打开另外一个实例,这样我必须关闭正在运行的Firefox。而且由于我比较喜欢打开非常多的Tabs来保持浏览状态,所以关闭Firefox让我很不爽,再说,因为重新启动的Firefox里面带了很多的Tabs,所以经常造成实际运行的JsUnit test发生随机性的超时错误,这个就不能容忍了,因为这无法保证我们的信心。

那么,为什么FF不能启动多个实例呢?原因是它们共享同一个Firefox的profile,所以没法多个实例并发访问。但是通过命令行参数是可以创建多个profile给firefox的,简单了。不过遇到的问题是JsUnit的ant任务会检测BROWSER_PATH是否存在,所以如果我把带参数的命令行写到环境变量里面Ant无法检测到这个文件就会报错。那么如果关闭检测可以么?还是不行。因为JsUnit的StandaloneTest里面实际最后会调用DefaultProcessStarter的execute方法,这个方法调用Runtime.getRuntime().exec(command),这个实现非常直接,不过因为parameters如果直接写到命令行里会发生文件无法找到的问题(应该用数组将命令和参数传入)所以没有办法传入,还是无法运行。

放弃hack吧,我可以修改Ant task和JsUnit的方法,但是绝对不好,因为这个hack没有提交回去的意义。
所以换个思路,这样做:我们去写个shell来解决它。

先在终端运行/Applications/Firefox.app/Contents/MacOS/firefox-bin -CreateProfile jsunit,这时候会弹出窗口让你确认创建这个profile,选择一下不使用extensions和各种工具条,这样减少这些设置对测试的不良影响。
然后在你的home目录创建一个firefox.sh,里面写上:

/Applications/Firefox.app/Contents/MacOS/firefox-bin -P jsunit $1

前提是你的Mac使用的是默认的bash,否则修改$1为对应的引用字符。然后chmod firefox.sh 555,让它可以运行。

下面就是修改你的~/.profile:

export BROWSER_PATH=/Users/[User path]/firefox.sh

source ~/.profile让修改生效再运行JsUnit就OK啦。如法炮制想开几个Firefox实例都可以啦。同样方法也适用于让Firefox2和Firefox3共同运行!非常简单。还可以做到开发和浏览分开……以此类推。

回顾一下JsUnit的代码写的不好,如果像Selenium一样能够自动创建一个profile就好了,因为那样可以减少测试之间的影响,还可以让Selenium并行执行。我想,如果有空我可以做一下这个工作:D

Yahoo的“前端优化突破” slide共享

这是一篇很好的讲前端页面优化的slide,当然有不少内容我们都看过很多遍了,我以前做过的:

里面的内容也有一些重复。不过这次我还是很有收获,比如IBM的Page detailer就是个不错的profiler工具。还有使用document.getElementsByTagName(‘*’).length简单判断dom复杂度的方法也是不错的。结果是:

  • 新浪首页是3109
  • 财经首页是4260
  • 新浪邮箱是169
  • 纸条箱251
  • sohu是2k4
  • netease是1k4

很有意思。

结绳记事。

写javascript单元测试是挺爽的事,可惜不要在safari上?

对于javascript来说,通过单元测试,你也可以实现TDD。对你非常有好处,一是减少了js变动带来的代码退化问题,另外一方面是TDD可以改变你设计程序的方式。
举个简单的例子,写javascript很多情况下是和BOM(也就是文档模型)和DOM打交道的,这样可以说javascript程序很容易与dom高度耦合,这样的程序运行起来没有问题,但是应对需求变化的能力会比较地。但是根据人的思维方式,javascript很多时候是先出页面,然后根据页面逐渐调试着写出javascript,对于开发者来说,脑子里并不是真正的清楚自己要什么,而是在想界面的结果……这样产生的高耦合代码不容易测试,也难以面对多变的界面。
所以,换一种方式思考。如果用单元测试的方式去写,你就需要考虑程序的可测试性,这会细化你的程序的模块粒度,因为细粒度的抽象容易单元测试。同时由于js单元测试的页面是mock出来的,所以一般都会尽量的简单,这样会减少程序远对界面的依赖。同时由于界面的可测是性问题,也许会减少对element的style的修改,转而使用语义话的css。例如如果一个元素高亮,你可以element.style[‘font-size’]=’bold’;element.style.color=’red’l….,当然你也可以element.addClassName(‘highlight’),然后那些不好验证的界面的约束条件可以放到css里面去,放给可用性和用户验收测试去验证。这样的单元测试的验证条件(assertion)会简单很多,如果写过单元测试的朋友肯定会有感触的。
那么,常见的Javascript的单元测试框架有JsUnit和scrit.aculo.us的单元测试框架两个。前者方便用ant调用和分析结果,适合使用了ant的项目。而后者的优点就是界面好看,直接运行产生的报告清楚漂亮。所以小型项目我倾向后者,而大型项目我倾向前者。当然,由于js的动态特性,其实做个单元测试框架非常简单,所以自己动手也无妨。关键是要写,而且争取做到测试先行。

前面是个引子,其实写这个的原因是今天上午的一个郁闷的事。
前面的blog entry说道我升级了Firefox3,结果遇到了getElementsByClassName问题。但是今天换到另外一个项目组,没有用那个方法,程序也正常。可是我TDD的写一个新的feature的时候却发现可爱的JsUnit的testRunner在Firefox3里面无法工作,从firebug里面看到了一堆安全性问题。估计是firefox3的新安全模型造成的吧。那么,由于firefox3覆盖了firefox2,所以难道我没法写程序了?当然不可以,还有safari嘛。马上开始去写测试了,写好测试执行测试,发现红条。嗯,很满意,因为TDD的红-绿-红-绿的节奏就是这样的。然后我开始去写实现来满足这个测试……结果忙活了一上午就是不行……而且发现一些原来的测试也无法通过了……我仔细寻找问题,diff修改的内容,可是最后实在没有发现任何让它不能通过的原因,因为手工在firebug里面都已经验证了写的实现是没有问题的呀……崩溃。此时我突然想起来我们的持续集成服务器里面没有跑safari的JsUnit测试……也就是说不能确定在safari下全绿(此时的背景是我们的持续集成显示全部绿色,也就是说所有的测试都可以同过,包括windows和linux平台还有IE及Firefox),那么我可能衰了。马上开动camino(靠,一上午都忘记用它执行JsUnit了,因为我的习惯是Camino里面保存to read list),运行一下全绿。很兴奋,但是感觉刚才寻找问题的1个多小时被无辜的浪费了,心疼呀。

那么,请注意啦,我只是想提醒,jsUnit可能不能在safari下正常工作(大部分测试没有问题,少量在其它浏览器正常的测试在safari下无法工作),我用的是safari3.0.4……

Firefox3 beta2带来的getElementsByClassName问题

Firefox3解决了一些内存泄露问题,或者说主要的是解决了一些长时间运行以后的内存占用问题。由于使用mac的习惯是休眠不关机,所还是在正式版出来之前选择了升级到firefox beta2。
那么作为开发人员肯定遇到firebug不能用的问题,还好,我们可以用firebug的beta版本来解决这个问题,当然这个firebug还是会出现莫名其妙的问题,无所谓啦,不久就会发布正式版本了。
这些都是废话,问题集中在firefox beta2提供了原生的getElementsByClassName方法,而我们大家都很愿意使用的prototype库正巧也给Element封装过getElementsByClassName方法,看起来两者的作用是一样的,但是不巧……其实两者还是有很大差别的,所以造成了如果你的项目正巧用了getElementsByClassName方法,而后你又用习惯的prototype方式便利了它……那么你的程序就不能在Firefox3 beta2工作了……
我们以前的习惯就是不用考虑新浏览器的javascript兼容行问题,只需要考虑向后兼容,但是这次问题还是出现了变化。
其实原因是简单的:
1、Firefox3实现的原生getElementsByClassName方法返回的不是javascript数组(Array),而是html element collection,这个东西可以用标准的方式遍历,但是却与Array没有共同的prototype(这个指javascript里面的原型继承的prototype)。所以,很不幸,如果你用了Prototype库的那个返回Array的方法写程序,然后使用了Array增强方法里面的first()等方法或者Emmerable里面的each()等方法,那么程序一定会出错。此时你可以$A一下这个html element collection,但是这不是好方法。因为其实Prototype在1.6以后就不推荐使用getElementsByClassName方法了。
2、那么解决的方法就是使用select方法代替原来的getElementsByClassName方法,不过记得要给参数的前面加个”.”。比如oneElement.getElementsByClassName(‘someClass’)应该修改为oneElement.select(‘.someClass’)。而其实select方法由于接受css selector语法,可以实现更强大的选择操作。

嗯,非常废话。
那么简单的说:如果你在Firefox3里面发现你的基于Prototype库的js程序的getElementsByClassName以后出现了method null的错误,那么你需要用select方法代替它。且考虑到长时间的不兼容状态,最好完全消除你的程序中的getElementsByClassName同名方法调用,不关你用什么js库。