qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

关于工作效率的心得分享

 这是去年11月底在小组里分享过的工作效率心得,在这里也跟大家分享一下工作“快”感哈哈。我相信大家应该都有过工作效率的些许烦恼。而这个效率啊伴随我很长时间的痛苦。每每到PDI的时候领导必提效率有木有?自认为快是不算的,必须领导和客户方说了算,对于当年校招进来的毛驴,是一件泪崩漫长的提升过程。整天琢磨如何快,多快的速度才算快,恨不得快到连参照物都变得动感模糊。时间是把杀猪刀,不仅催人老还得与它赛跑,经过这几年的自残式磨练,也早该总结总结。
  一、 懂得整理需求
  效率是先把重要的事情优先排序来完成,好的整理习惯是可以提高效率的。当需求很多的时候是可以通过整理需求有条不紊的进行。只要了解哪些是重要和紧急的事情,就能轻松的决定先处理什么。这个相当于是做项目管理的事情,要善于管理自己的工作。建议学习佐藤可士和的《整理术》。
  例如:本周产品经理们提了很多需求:@#¥……&%……&%……
  可这样做:
  1、列出所有需求
  ABCBBD
  2、归纳类型
  ABBBCD
  3、了解份量
  A(传输功能设计):总办需求
  BBB(图标类设计): 迭代需求,排一起绘制
  C(新增界面设计): 迭代需求,需较多时间设计
  D(功能宣传图):迭代需求,版本发布的新功能宣传
  4、明确期限
  假如级别为High、Mid、Low…
  A(传输功能设计):总办需求—非常紧急(今天)—High
  D(功能宣传图):迭代需求,版本发布的新功能宣传—周三—Mid
  C(新增界面设计): 迭代需求,需较多时间设计—本周—Mid
  BBB(图标类设计): 迭代需求,需较多时间设计—本周—Low
  需求从散到整,从大到小,从繁到简,从紧急到迭代这样去处理就会变得清晰可观。
  二、要有探究需求真相的精神
  可能很多人也曾拿到需求就马上画稿,孰不知是不断重来的经历。表面应付需求是无法解决问题的,要先了解用户或老板的核心诉求,找出诉求的背后真相,才能够有合适的方案去解决,才能快刀斩乱麻。本质问题得到解决便不会有重复工作量而拖延了本来的交付期限。还有就是有一些需求根本不是需求,比如老板只是随便几个建议。所以要思于前,行于后。
  三、练好刀工才能游刃有余
  技能的熟练会提升工作效率。就像切土豆丝,要切成厚薄、细条一致又要快,那是必须有一双好刀手。做图也一样,就像画图标,要好质量的呈现,你明确了所有要注意的细节,绘制的时候会非常快速准确的完成,一步到位。要使车子走得快,就得给轮子勤上油。
  四、在高速重复工作中提取模版
  模型套用会提升工作效率。在工作中一定会有很多重复式工作,比如标注规范和切图这样必不可少但却繁琐没营养的工作,可以提取模型工具去套用快速完成。规范标注,可以根据同类窗口、内容统一规范而不要再差异设计和重复标注。切图可以利用软件工具输出,比如slicy或PS插件,这是要善于把所有图标控件整理成库,规范好命名,便可一键批量切图。还有比如tips弹窗界面,根据目前所有情况,做典型1~2个模版,其他直接套用,之后可不再经过设计,开发根据模版直接实现。
五、学会聚焦与屏蔽
  1)专心致志做事
  2)屏蔽干扰消息
  早上应该是比较难聚焦做事情的时间段,到办公室第一件事往往不是开始工作。比如没睡醒,各种早上新闻,查看昨天消息等。工作时被很多信息干扰,群消息,邮件,微信,弹窗广告等,包括自己有事没事的逛微博、各路网站,很容易被外界风吹草动的影响而分心,一天班下来才发现其实本应该上午就可以完成的工作,还得加班或干脆拖到明天。这都是懒惰病和拖延病,得治。所以要先学会屏蔽杂七八,然后全身心聚焦到工作的内容里,最后你会发现提前完成了很多工作。聚焦利于把需求思考得深一些,把工作做得精细些,得出的加分项就是完成的质量随高。
  六、多任务处理
  能够有全神贯注的状态,也能够有“一心多用”的功能。这个要求比较高,我也好难描述。拿一个妇女做例子,她可以一边炒菜一边看管在旁边玩耍的孩子,一边打电话给闺蜜聊去哪里逛街和去哪里美甲,所以女人是一心多用的高手,灵活性非常高。我也常是一心多用的处理我非常繁多且接踵而来的任务,不然总觉得时间不够用,或者为了省下后面的事情做些自己想做的事,比如思考、总结,或不用加班。但是建议在做非常繁多、零碎又不需要深度思考的需求时可以使用这招。最好不要持续这样高度紧张的一心多用,长期只会感觉越来越累,而且打乱节奏,所以要懂得变化各种技巧。
  七、敏捷响应
  紧急需求时,能够快速响应,敏捷处理。这个时候是最能影响客户方的感受。还有一些几乎每天都会有的临时小小需求和bug,只要是合理的,就去免讨论部分,快速输出也是可以的。有一次解决的小需求从修改到输出给产品只花了28秒,帅呆了自己惊呆了客户,这就是在日常中慢慢积累的效率好评。
  八、拍板与结论的必要性
  1)需要一个有力决策的人
  2)控制范围并迅速得出结论
  一个有力决策的人可以提高工作效率。由于评审层级关系的复杂流程,需求确认要漫长时间是家常便饭,所以如果有机会碰到老大们都在一起,有些事情能抓住时机沟通和拍定的机会就迅速做。但不一定老大拍板这个事情就一定是对的,有些复杂的事情安排在会议上拿出来大家讨论,但是一定要有靠谱结论,如果没有意识的控制,没完没了的讨论就是低效率,要善于控制范围、节奏又能迅速总结的人,能提高解决问题的效率。
  九、当自己的事情做
  只有当作是自己的事情做,意愿才会更强,而且也会有一些自己的想法,想着这件事情是有自己的成绩在里面的,就会更有动力。换位思考,当作是锻炼的机会。
  十、保持健康和清醒
  健康的身体状态,健康的作息,健康的工作频率是效率的革命前提。关于清醒的办事用歌德的一句话:在今天和明天之间,有一段很长的时间,趁你还有精神的时候,学习迅速地办事。
  总结:
  效率是由综合因素影响的,但跟个人办事风格有很大关系。其实效率是可以量化,也可进阶式自我考察,比如接手一个界面风格设计从要花1个月,到半个月、1周、两天就可以完成;码一周代码变成1天就可码完。这就能看到效率的有效提升。前两年长期搭档的几位即通产品经理,给我封了个快刀手称号,开发也说我以快著称,才慢慢发现自己这方面的成长与进步。希望这点经验总结能够给新人带来帮助,终极祝福永不改稿永不加班!

posted @ 2014-07-15 10:14 顺其自然EVO 阅读(159) | 评论 (0)编辑 收藏

如何考查自己的测试水平?

  提问:如何考评自己的测试水平
  回答:个人认为可以通过如下几项进行综合考评:
  1.通过自己编写的测试用例发现的有效bug
  可以考评的是测试人员编写测试用例的水平(编写测试用例属于测试人员的工作职责范畴)。当然此项并不一定能证明测试人员水平,还与开发人员能力有关,如果开发人员代码写的好,测试人员测试出来的bug自然就少。所以需要用多个项目进行考评
  2.在项目中测试的总的有效bug数量
  同上测试的bug多,并不一定能证明测试人员水平,还与开发人员能力有关,如果开发人员代码写的好,测试人员测试出来的bug自然就少。所以需要用多个项目进行考评
  3.漏测率
  计算公式:客户反馈的Bug数 / Bug总数(测试+客户) *100%
  可以使用多个指标进行衡量,如以下几点:
  1)用例包括而未发现的bug数/用例总数 (衡量用例执行质量,执行人员的测试效果)
  2)版本交付后又发现的bug数/项目bug总数(衡量总体测试效果,也是重要的产品质量,项目质量指标)
  3)用例不包括而发现的bug数/bug总数 (衡量用例质量)
  4)严重bug在测试周期的分布(后期严重bug比例较重,则显然整个项目组都比较被动,虽然bug最终被发现了,但仍是一种“漏测”行为)
  值得说明的是
  1)上述指标异常并非只是测试人员的责任,开发人员也可能会在后期引入严重缺陷。
  2)测试用例不包括而发现的bug,一般不认为是测试失误,而是测试用例设计和评审的失误。
  3)测试用例包括而未被发现的bug,测试人员应付全责。
  4.对bug的分析和排查水平
  能从表面现象发现内部结构和规律,能够快速准确地对bug进行定位;
  5.对软件测试基础理论与技术的了解程度
  首先是精通各种黑盒测试技术,能够进行测试用例设计、测试执行、编写缺陷报告;
  其次是熟悉软件测试流程和测试过程管理,能够编写测试计划,具备组织测试工作的能力;
  还有熟悉白盒测试技术,能够手工或利用相关工具进行单元测试
  6.能否能够胜任白盒测试、 自动化测试性能测试
  7.对缺陷管理工具、自动化测试工具等等的使用程度
  8.是否会搭建数据库
  目前大部分应用软件都离不开数据库,熟练掌握SQL Server、Oracle、DB2等一种或多种数据库系统的使用,是否会搭建数据库取决于测试人员是否能够熟悉使用SQL语言
  9.对WindowsLinux、Unix等大型主流操作系统的使用和应用开发是否能够熟练掌握,包括一些网络的基础知识。
  只有熟练掌握Windows、Linux、Unix等大型主流操作系统的使用和应用开发,才具备快速进行应用系统部署和测试环境以及网络的搭建。
  10.是否善于对软件质量的分析和对测试过程进行度量,以及编写软件质量报告和测试分析报告的能力等等。

posted @ 2014-07-15 10:12 顺其自然EVO 阅读(195) | 评论 (0)编辑 收藏

Java基本类型与对象类型的区别导致的Bug剖析

本文中所提到的基本类型是指类似 int,long等,而对象类型是指Integer,Long等。
  基本类型和对象类型第一个最大的不同在于初始化的值不同。int 初始化为0,Integer 为null。在一个线上产品故障的排查过程中发现根本原因在于开发同学把数据库DO对象的一个字段从int 改成了Integer引起的,因为int 类型可以正常的初始化,而Integer 对象的时候不能正常插入,导致了线上产品故障。
  正是由于初始化的值的不同,也导致了在进行逻辑比较的时候,对象类型很容易出现空指针异常:
  基本类型可以直接进行逻辑判断:
int num;
if( num >0 ){
//todo
}
  这样的代码不会有空指针的异常,但是如果是如下代码:
Integer num;
if( num >0 ){
//todo
}
  就会有空指针异常。这种异常特别容易出现在web的参数处理上:
  例如:
public class MyTest {
@Autowired     private HttpServletResponse response;
public void execute(@Param("pageSize") Integer pageSize) throws Exception {
//todo
//这里容易空指针异常
if( pageSize<=0 ){
pageSize = 1;
}
//todo
}
}
  第二个不同在于基本类型没有可执行的方法,而对象类型支持很多封装的方法,例如Integer 对象 可以使用toString()的方法。
  第三个不同在于基本类型不能作为List或者Map的类型,例如:
  List<int>  numList = new ArrayList<int>();  //这样是不可以的。

posted @ 2014-07-15 10:10 顺其自然EVO 阅读(136) | 评论 (0)编辑 收藏

解读分布式对象存储系统Sheepdog性能测试

  Sheepdog是一个分布式对象存储系统,专为虚拟机提供块存储,号称无单点、零配置、可线性扩展(省略更多优点介绍)。本文主要关注其性能究竟如何,测试版本为目前的最新稳定版0.7.4。
  测试环境
  节点数量:6个
  磁盘:各节点都配备7200转SATA硬盘,型号WDC WD10EZEX-22RKKA0,容量为1TB,另外测试节点(即用于启动虚拟客户机的宿主机)多配置一块SSD硬盘,型号INTEL SSDSA2CW300G3,容量为300GB
  网络:测试节点配备PCI-E双千兆网卡,通过bonding进行负载均衡
  文件系统:ext4fs,挂载参数:rw,noatime,barrier=0,user_xattr,data=writeback
  sheepdog集群
  可用空间:6个节点各分配100GB,总共600GB
  对象副本数量:3个,客户机实际最大可用空间为200GB,接近项目规划的容量
  对象缓存:使用SSD硬盘,分配256GB,可完全缓存客户机文件系统,随测试项目需要挂载或卸载
  虚拟客户机
  资源:4GB内存,4个逻辑CPU
  使用qemu 1.7启动,支持KVM加速、virtio
  两大测试项目
  IOPS:主要考察较小数据块随机读和写的能力,测试工具fio(sudo apt-get install fio),测试文件固定为150GB
  吞吐量:主要考察较大数据块顺序读或者写的能力,测试工具iozone(sudo apt-get install iozone3),测试文件从64MB倍数递增到4GB,如没有特别说明,测试结果取自操作512MB的数据文件
  除特别说明,qemu启动虚拟客户机命令如下:
  qemu-system-x86_64 --enable-kvm -m 4096 -smp 4 -drive file=sheepdog:mint,if=virtio,index=0 \ -drive file=sheepdog:data,if=virtio,index=1 -net nic,model=virtio -net user -vnc :1 -daemonize
  IOPS测试
  测试须知
  关于SATA硬盘的IOPS
  测试用的普通7200转SATA硬盘,官方公布其平均寻道时间10.5毫秒,所以,根据以下公式,理论上的IOPS值最多只有65。
  实测结果,在单线程同步IO的情况下(下图深蓝线),也是65,在多任务(紫线)或者使用异步IO(黄线)的情况下,由于操作系统IO调度,IOPS能够达到80到100之间。如果进行读写操作的文件远远小于SATA磁盘大小,缩小了存储单元寻址范围,减少了全磁盘寻道时间,也能提高IOPS,例如,测试文件大小占磁盘大小的1/8的时候,IOPS最高在90左右(浅蓝线)。
  关于SSD硬盘的IOPS
  测试用的SSD硬盘,在单线程同步IO的情况下,IOPS最多能够达到9000左右(下图深蓝线),在多任务(紫线)或者异步IO(黄线)的情况下,最多能够达到40000-50000之间。由于构造原理不同于传统磁盘,减小测试文件大小并不能明显提高IOPS。
  关于读写比例
  由于大多数业务场景既有读操作,也有写操作,而且一般读操作比写操作多很多,因此,读写混合测试项目中的读写比例设定为4:1。一般业务很少有完全随机写的情况,因此不进行只写测试。

关于电梯算法
  虚拟的客户机操作系统的IO调度算法使用noop,网上有资料能够提高IOPS,但是实测效果不明显,因此最终报告并没有把它单列出来。
  IOPS测试1:不使用对象缓存,只读测试
  单线程同步IO的情况下(下图深蓝线),sheepdog的IOPS差不多达到100,比单节点单SATA硬盘高,究其原因:客户机中的测试文件为150GB,共有三个副本,即共占450GB,集群有6个节点,平均每个节点75GB,而各个节点所在磁盘容量为1TB,仅占其1/13,因此,无其它IO任务的情况下,IOPS会比全磁盘随机操作高。
  减少客户机的测试文件大小为原来的1/8,即19GB,IOPS能够达到130-140左右,验证了无其它IO任务的情况下,测试文件越小,IOPS越高(浅蓝线)。
  恢复客户机的测试文件为150GB,在多任务(线程数量为10,紫线)或者异步IO(队列深度为10,黄线)的情况下,IOPS可达230-250。
  250左右是否该sheepdog集群的极限?进一步换其它numjob和iodepth的组合进行测试,答案是肯定的,测试结果都在250左右,以下是线程数量为8,异步IO队列深度分别为1、4、16、64、256的测试结果,线程数量增加到16,测试数据并没有提高。
  IOPS测试2:使用对象缓存,只读测试
  单线程同步IO、使用对象缓存且缓存命中率100%的情况下(下图蓝线),sheepdog的IOPS最高可达6000,对比相同条件下SSD硬盘的测试结果(最高9000),还是有些损耗。
  通过多任务(紫线)或者异步IO(黄线)的方式提高并发IO数量,在缓存命中率100%的情况下,IOPS可以提高到40000-50000,基本与相同条件下SSD硬盘的测试结果相当。
  注意上图为对数刻度,且没有数据值,很难比较数值大小,下图省略了一半数据块,但是标上了数据值。
  之所以强调缓存命中率,是因为在不完全命中缓存的时候,IOPS下降很厉害,低于80%的话,可能还不如没有对象缓存。下图是sheepdog对象缓存命中率与IOPS的关系图,其中,测试的数据块大小从512B倍数递增到512KB,缓存命中率是在测试过程中,根据sheepdog的cache目录中已有的对象文件数量估算的平均值,IOPS值是读取数据块操作在估算的缓存命中率条件下测量的平均值。
  50000是否该sheepdog集群的极限?仿照上面的方法进行测试,答案也是肯定的,测试结果都在50000以内,以下是线程数量为8,异步IO队列深度分别为1、4、16、64、256的测试结果,线程数量增加到16,测试数据并没有提高。
  IOPS测试3:不使用对象缓存,读写混合测试
  读写混合测试的IOPS比只读测试的结果,总的来说要低一些,而且起伏较大,需要多次测试计算其平均值。
  单线程同步IO的情况下(下图深蓝线),能够达到80-100。
  减少客户机的测试文件大小为原来的1/8,即19GB,IOPS能够达到100-120(浅蓝线)。
  恢复客户机的测试文件为150GB,在多任务(线程数量为10,紫线)或者异步IO(队列深度为10,黄线)的情况下,IOPS大多在180-250之间,但也有时候只到160左右。
  IOPS测试4:使用对象缓存,读写混合测试
  单线程同步IO、使用对象缓存且缓存命中率100%的情况下,IOPS差不多达到4000。
  通过多任务或者异步IO的方式提高并发IO数量,在缓存命中率100%的情况下,IOPS可达10000-20000之间。
  注意上图为对数刻度,且没有数据值,下图省略了一半数据块,但是标上了数据值。可以看到,大数据块读写混合的情况下,多任务或异步IO的IOPS可能还不如单任务同步IO

吞吐量测试
  测试须知
  关于SATA硬盘的吞吐量
  普通7200转SATA硬盘的吞吐量数据,对比一下后面SSD硬盘的吞吐量数据,说明传统磁盘顺序读写的能力还是相当出色的。
  关于SSD硬盘的吞吐量
  大数据块(512KB以上)顺序读,吞吐量可达250MB/s-270MB/s,顺序写,吞吐量可达200MB/s-210MB/s。
  小数据块(16KB)顺序读,吞吐量也有140MB/s,顺序写大概120MB/s-130MB/s。
  无优化sheepdog的吞吐量
  在软硬件不做任何优化的情况下(不绑定双网卡、不使用virtio、以默认参数启动sheepdog),sheepdog的吞吐量数据比较低:
  顺序写
  数据块大小为16KB,吞吐量13-14MB/s
  数据块大小在256KB-4MB之间,吞吐量30-35MB/s
  顺序读
  数据块大小为16KB,吞吐量25MB/s左右,如果数据文件大小超过可用内存,降低到20MB/s
  数据块大小在512KB-4MB之间,吞吐量125-140MB/s,如果数据文件大小超过可用内存,降低到80MB/s以下
  以下两图,上图为数据文件512MB的测试数据,下图为数据文件4GB的测试数据
  注,不使用virtio方式启动sheepdog的命令行参数为:
  qemu-system-x86_64 --enable-kvm -m 4096 -smp 4 -drive file=sheepdog:mint,index=0 \ -drive file=sheepdog:data,index=1 -vnc :1 -daemonize
  吞吐量测试1:不使用对象缓存
  从测试结果看,使用virtio方式启动客户机有利有弊:
  提高了中小数据块读写操作的吞吐量
  当数据块大小在16KB-512KB之间时,顺序读的吞吐量提高10MB/s-30MB/s不等
  当数据块大小在16KB-256KB之间时,顺序写的吞吐量提高5MB/s-8MB/s不等
  大大降低了超大数据块读操作的吞吐量,至于为何virtio在这种情况下会降低测试的吞吐量,尚不清楚
  当数据块大小达到2MB或者4MB时,顺序读的吞吐量降低80MB/s-90MB/s
  以下两图,上图为512MB下的测试数据,下图为该情况下,与无优化sheepdog读写512MB数据文件两者吞吐量之差。
  吞吐量测试2:使用对象缓存
  使用了对象缓存,顺序读写的吞吐量与数据文件大小没有直接关系,即使数据文件大小超过可用内存。
  同时也使用了virtio,并没有因此降低读取大数据块的吞吐量。
  由于SSD硬盘的特点,数据块越大,吞吐量越高,在数据块大小为4MB的情况下,顺序读的吞吐量可达240MB/s,顺序写也有130MB/s;如果数据块大小只有16KB,顺序读和顺序写的吞吐量分别只有55MB/s和35MB/s。
  总结sheepdog性能
  以上为sheepdog集群节点数为6个,且各节点都只挂载一块SATA硬盘的情况下测得的结果,如果增加集群节点数,并且每个节点都挂满硬盘,IOPS应该会更高,而吞吐量取决于缓存和网络带宽,应该不会有明显变化。

posted @ 2014-07-15 10:09 顺其自然EVO 阅读(242) | 评论 (0)编辑 收藏

移动前端开发之数据库操作篇

 在移动平台开发中,经常会有大数据存储与交互的操作,在以webkit为内核的浏览器中,提供了一个叫作WEBSQL的数据库。这让我们前端也可以像php等程序语言一样,进行数据库的读写操作。Web Storage存储本地数据的方法目前可以在许多主流浏览器、平台与设备上实现,与之相关的API也已经标准化,但是,Web Storage存储空间是有5MB,键值存储的方式带来诸多不便,未来本地存储也不仅仅是这一种方法。其中最为熟知的就是Web SQL数据库,它内置SQLite数据库,对数据库的操作可以通过调用executeSql()方法实现,允许使用JavaScript代码控制数据库。这样一来,前端应用就有了一个更为广阔的天空。
  打开与创建数据库
  如果要通过WebDb进行本地数据的存储,首先需要打开或创建一个数据库,打开或创建数据库的API是openDatabase,其调用的代码如下所示:
  openDatabase(DbName,DBVersion,DBDescribe,DBSize,Callback());
  参数分别表示数据库名称,版本号,描述,数据库大小(字节为单位),创建或打开成功后执行的回调函数。
  主要用到的有如下三个方法:
  1、openDatabase:这个方法使用现有数据库或创建新数据库创建数据库对象。
  2、transaction:这个方法允许我们根据情况控制事务提交或回滚。
  3、executeSql:这个方法用于执行真实的SQL查询。
  下面我们就以具体的示例进行演示。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Database API</title>
</head>
<body>
<script type="text/javascript">
//数据文件在谷歌浏览器下,默认存放的位置
//"C:/Users/Administrator/AppData/Local/Google/Chrome/User Data/Default/databases/1";
if(window.openDatabase){
//如果test数据库存在,则打开,否则会自动创建
var db  =  openDatabase("test", "1.0", "Database test", 1024 * 1024);
//创建一个学生表
var sql = 'CREATE TABLE  if not exists "student" ('+
'"_id"  INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,'+
'"name"  TEXT NOT NULL,'+
'"age"  TEXT,'+
'"mark"  TEXT)';
//写入一条数据
var sql2 = 'insert into student (name,age,mark) values ("frog",1,"nice frog") ';
//取出一行数据
var sql3 = 'select * from student';
//预处理语句的用法
var sql4 = 'insert into student (name,age,mark) values (:name,:age,:mark)';
/**
* 预处理语句在php中也有类似的用法
* $stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
* $stmt->bindParam(':name', $name);
* $stmt->bindParam(':value', $value);
*/
var name = 'aron';
var age  = 29;
var mark = 'hello world';
exec(sql,function(rs){
//这里之所以要用回调,
//是因为数据操作过程是异步的
exec(sql2,function(rs){
exec(sql3,function(rs){
console.log(rs.rows.item(0))
})
exec2(sql4,[name,age,mark])
})
});
//简单封装一个数据操作的方法
function exec(sql,callback){
db.transaction(function(tx){
//中间这个[],在预处理语句中绑值
tx.executeSql(sql,[],function(tx,rs){
//success
callback && callback(rs);
},function(tx,msg){
//error
console.log(msg);
});
});
}
//支持变量邦定
function exec2(sql,para,callback){
para = para || [];
db.transaction(function(tx){
tx.executeSql(sql,para,function(tx,rs){
//success
callback && callback(rs);
},function(tx,msg){
//error
console.log(msg);
});
});
}
}
</script>
</body>
</html>
  以上代码另存为html文件,在谷歌浏览器上,打开控制台,可以看到运行的结果。
  局限性:
  可惜,我测试了下程序员最喜欢用的Firefox浏览器,可惜它并不支持这种本地数据库SQLite,且有可能被indexDB所替代的趋势。

posted @ 2014-07-10 19:40 顺其自然EVO 阅读(631) | 评论 (0)编辑 收藏

Java系统开发框架

 做项目的时候,最需要快速开发框架来辅助。好的快速安全的开发框架,使得开发起来得心应手。只需要关注业务的细节,业务流程出来了,设计完成,就可以快速开发实现。 最近在做的一个项目就是如此,本身是一个开发平台,集成了e表报表工具,使得复杂的多变统计分析报表做起来事半功倍。 集成eworkflow工作流系统,主要业务流程都可以先图形化的设计好,表单可以用eform的电子表单也可以集成定制的页面。多变的业务流都可以先定义好,图形化的方式展现出来,业务流程实例也可用图形的方式追踪管理。
  java开发框架主要以后台的MVC框架为主,有统一的入口portal, 环境类,数据库连接方式。 页面请求对应的后台handler类,输入输出元素的传递。 DB操作的封装,复杂的sql查询直接用统一封装的jdbc操作工具实现。单表的增删改查用一个单表操作的orm工具。单表的orm操作工具,有生成表类对象的工具,封装的通用组合查询字段的方法,单表保存方法,删除方法,按主键查询,给字段赋值的方法。 前台页面部分集成jquery框架,jsp页面中集成标签库,使得代码和js分离,页面更容易维护。
  系统开发框架图
  ui部分,通过input和output对象达到页面和后台handler类的交互。
  BH类实现业务Handler抽象类,主要为各业务模块提供实现方法。
  BL 部分实现业务规则和业务逻辑和调用db层的操作
  DB层有封装的jdbc 的操作和单表的orm映射工具,复杂的操作可以用jdbc的封装来完成。
  对象关系模型图
  1、Portal:系统统一入口,在系统中扮演Controller角色,负责对handler类和jsp页面定向的调度以及数据库连接的分发。
  2、InputObject:输入对象,负责收集页面中提交的元素。
  3、OutputObject:输出类,负责将经处理的结果集、提示信息、异常信息传输到前台页面
  4、BusinessHandler:具体模块的controller类,为每个业务提供具体的操作方法
  5、Peer类:db访问类
系统运行时序图
  上图中访问db返回resultSet不对,访问db返回的是List<Map<String,Object>>, 一条记录就是List一个元素,字段名为map对象的key,字段值就会Object。
  先记录这些了,有对这java开发框架感兴趣的话,以后再续上更多的资料

posted @ 2014-07-10 19:39 顺其自然EVO 阅读(252) | 评论 (0)编辑 收藏

使用JUnit来测试Java代码中的异常

  使用JUnit来测试Java代码中的异常有很多种方式,你知道几种?
  给定这样一个class。
Person.java
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 0 ) {
throw new IllegalArgumentException("age is invalid");
}
this.age = age;
}
}
  我们来测试setAge方法。
Try-catch 方式
@Test
public void shouldGetExceptionWhenAgeLessThan0() {
Person person = new Person();
try {
person.setAge(-1);
fail("should get IllegalArgumentException");
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(),containsString("age is invalid"));
}
}
  这是最容易想到的一种方式,但是太啰嗦。
JUnit annotation方式
  JUnit中提供了一个expected的annotation来检查异常。
@Test(expected = IllegalArgumentException.class)
public void shouldGetExceptionWhenAgeLessThan0() {
Person person = new Person();
person.setAge(-1);
}
  这种方式看起来要简洁多了,但是无法检查异常中的消息。
  ExpectedException rule
  JUnit7以后提供了一个叫做ExpectedException的Rule来实现对异常的测试。
@Rule
public ExpectedException exception = ExpectedException.none();
@Test
public void shouldGetExceptionWhenAgeLessThan0() {
Person person = new Person();
exception.expect(IllegalArgumentException.class);
exception.expectMessage(containsString("age is invalid"));
person.setAge(-1);
}
  这种方式既可以检查异常类型,也可以验证异常中的消息。
  使用catch-exception库
  有个catch-exception库也可以实现对异常的测试。
  首先引用该库。
  pom.xml
  <dependency>
  <groupId>com.googlecode.catch-exception</groupId>
  <artifactId>catch-exception</artifactId>
  <version>1.2.0</version>
  <scope>test</scope> <!-- test scope to use it only in tests -->
  </dependency>
  然后这样书写测试。
  @Test
  public void shouldGetExceptionWhenAgeLessThan0() {
  Person person = new Person();
  catchException(person).setAge(-1);
  assertThat(caughtException(),instanceOf(IllegalArgumentException.class));
  assertThat(caughtException().getMessage(), containsString("age is invalid"));
  }
  这样的好处是可以精准的验证异常是被测方法抛出来的,而不是其它方法抛出来的。
  catch-exception库还提供了多种API来进行测试。
  先加载fest-assertion库。
  <dependency>
  <groupId>org.easytesting</groupId>
  <artifactId>fest-assert-core</artifactId>
  <version>2.0M10</version>
  </dependency>

然后可以书写BDD风格的测试。
@Test
public void shouldGetExceptionWhenAgeLessThan0() {
// given
Person person = new Person();
// when
when(person).setAge(-1);
// then
then(caughtException())
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("age is invalid")
.hasNoCause();
}
  如果喜欢Hamcrest风格的验证风格的话,catch-exception也提供了相应的Matcher API。
@Test
public void shouldGetExceptionWhenAgeLessThan0() {
// given
Person person = new Person();
// when
when(person).setAge(-1);
// then
assertThat(caughtException(), allOf(
instanceOf(IllegalArgumentException.class)
, hasMessage("age is invalid")
,hasNoCause()));
}
  第一种最土鳖,第二种最简洁,第四种最靠谱。

posted @ 2014-07-10 19:26 顺其自然EVO 阅读(2539) | 评论 (0)编辑 收藏

互联网业务测试团队如果快速构建轻量级的自动化




 做测试,自动化是一个绕不开的主题,而且也是非常值得去做的事情,无论是对测试质量和效率的提升,还是对人的能力的锻炼,都是非常的有帮助。就目前个人观察到的情况,一些负责基础性组件测试的团队,比如底层平台、SDK等,或者是负责功能通用性高的产品,比如防火墙、邮件系统等,都可以做到比较高的自动化率,而且自动化测试开发的过程通常也没有那么纠结。相比而言,强应用层业务相关的测试团队,通常在自动化方面的开展要困难很多。我这里说的是比较全面的功能的自动化,不是指写几个简单的脚步覆盖几个功能,大的思路是接口层面,不涉及UI的,而且可以制作大规模的用例。
  主要有几个问题:
  1. 首要的问题是版本的节奏,互联网的产品版本的节奏非常快,一个20多人的测试团队一周发布上百个功能特性是一件很普通的事情。团队成员有非常多的精力消耗在这些功能版本上,需要快速的理解业务,构建测试环境,bug验证回归,以及发布上线等等,留给业务测试同学构建自动化用例的时间非常少。
  2. 功能非常的庞杂,而且有负责的业务流程,难以标准化。
  3. 说到自动化,大家会想到自动化框架,需要有一个比较好的自动化框架。自己开发或者维护过框架的同学可能都深有体会,这绝不是一件容易的事情,其工作量和持续的时间往往会超出我们的预期。我们是否有足够的测试开发人力能做自己的框架,以及业务能否承受这样的等待时间?
  互联网行业的变化很快,线上功能变化按天算,业务调整和组织架构调整按月看,做一个大的框架如果在部门层面还有可能,到了业务测试团队来说比较奢侈。很多方案是环境和需求逼出来的,细想下来,有两条路,一是看部门的框架,二是有没有轻量级的方案,可以快速的应用。
  这里结合我们自己的一些实践来看看,也是一点浅显的经验。
  部门有一些平台,但是有一些局限,主要是针对HTTP协议,另外对数据的准备支持不太好,也不支持DB的数据检查等功能,另外就是不太方面去定制和扩展,相比而言更适合外网的监控,评估下来准备想想别的办法。
  很多时候,过去的成功经验往往会让我们想复用,所以一开始的时候准备参照在以前公司做过的keyword driven的框架,把功能点封装成一个个的步骤,然后通过配置文件来构造用例,实现自由的组合和数据驱动。这条路验证过,而且看起来对现在的业务来说也没有什么问题,能走通,但是就目前团队的情况来看,开发一个这样的框架的代价无法承受。
  考虑了很久,于是有了现在更轻量点的做法,写出来给那些测试开发资源比较少的业务测试为主的团队参考。
  如果想要快速,但是又功能丰富,最好的办法就是抱一条粗腿,这里我们选的是JMeter。从2.2版本到最近的2.11,一直在关注和使用,发现JMeter的进步很快,版本活跃程度也很高,另外,它早就超出了一个HTTP性能测试工具的范畴,自动化测试其实是它另一个很大的主打,官方文档也提到。
  选中JMeter简单来说有几点:
  1. 接口大部分HTTP协议,而JMeter对HTTP支持非常全面,毕竟是做这个出身的。如果有些接口不是呢?我们的做法是用写adapter转换,这里不详述,所以写用例这边还是按HTTP协议走。
  2. 对数据提取和断言非常多的支持。还可以支持直连DB,可能需要补充额外的驱动jar包,但是没有技术障碍。
  3. 可以图形界面,极大的方便了用例制作和调试。以前的经验,我们做了命令行的功能点,后面为了大规模制作用例,还开发过web console,现在完全没有必要,这一部分也省掉了。
  4. 可以命令行执行,这就意味着可以方面的被粘合到我们的脚本里面。
  5. 输入和输出都是文本,jmx和存成的csv都是文本,极大的方便了脚本的处理和结果的parse。
  还有更多细节的点不多说了,有了这些基本可行性就很高了。想要独自开发上面这些功能得很大的代价,这也是轻量级能做到的主要原因。
  有了上面这些基本的骨架有了,但是要想想怎么整合成可复用和扩展的用例。
  基本的思路如下:
  1. 用例的分层,为了更好的复用和管理。CGI是最小的粒度,对应一个HTTP请求,完成一个很细节的操作。Function是一个对外有逻辑意义的历史,比如下订单,审核订单。TestCase是一个成品,TestSuite是一个用例的集合。
  每个子系统的目录组织如下:
  这里有个小的tricky的地方,为了部署的时候更灵活,希望脚本里面互相引用的文件是相对的路径,但是JMeter支持不太好,默认的根目录是bin,所以简单的做法是把整个自动化用例的目录copy到JMeter的bin下面。 2. 因为我们有多个系统,对应多个测试小组,大家各有专注,而我们电商的业务又是一个长链条。function层就是我们复用的基础,里面再包含对CGI层接口的调用。这里有点JMeter的技术细节,2.10开始建议用TestFragment来组织,而TestFragment是不能直接执行的,只是些积木,到了TestCase层面再用ThreadGroup现场组,才可以执行。
  下面是一个完整的TestCase,include了本系统和其他系统的一些function座位步骤。用例细节其实还有很多需要打磨的地方,比如命名规范,执行起来不容易。
  3. 自动化的报告,每次执行完了之后自动的发邮件报告出来。
 为了更好的定位问题,需要把每个接口执行的细节也暴露出来,点击详情可以看到调用的情况,包括request和response的数据。
  除了好处,也有一些不方便的地方:
  1. JMeter在include其他的jmx脚本后,不能直接在界面显示加载的内容,所以看不到被include的脚本里面的步骤,调试的时候不方便。不过好在JMeter可以一套binary启多个,并着看。
  2. 最后的结果报告里面,不能控制展示的粒度,是直接摊开成CGI层面的步骤,显得比较杂。
  目前我们执行的情况
  1. 目前覆盖了200多个CGI接口,以及100多个功能点,初步有4个系统挂接到了自动部署后的执行。用例还在进一步的扩展中,框架不需要改动。JMeter本身的稳定性还是不错的。
  2. 整个过程,不算制作用例的时间,我们实际投入的测试开发的人力合起来不到一个人月。
  3. 大部分业务测试同学都参与到了用例的制作,提升了对业务逻辑的理解,并且对部分同学来说,也补了HTTP协议等基础知识的课,实际动手比听听培训效果要好。
  从实际的效果和投入来说还是比较满意的。
  除了技术层面,自动化执行起来有几个核心的要点:
  1. 一定要强挂钩到测试和发布的环节。
  这一点看起来没那么重要,但是如果不希望自动化成为花瓶,就必须要这样做。像互联网产品这样快的节奏跑起来,任何花哨的环节会逐渐被洗掉,因为人员的配比和版本的数量,不是必做的东西慢慢就坚持不下来。所以自动化如果要能发挥效果,目前来看最合适的点是在每次自动部署后快速的刷一遍,在手工测试开始之前。而且如果要人工去点,这事儿时间长了也不靠谱,一定要把这个过程也自动化,版本在测试环境部署好了之后,自动化自动跑完,这就是强的挂钩。
  2. 报告也是要自动的,并且邮件抄送给相关的开发,测试和团队的负责人。我们现在的做法是跑完了脚本解析后自动的发邮件报告。
  3. 非100%成功的都要跟进。
  宁愿少而精,这个也类似broken window理论,如果能容忍一个用例失败,就会有2个,3个,也会让自动化慢慢失去意义。目前的做法是只要有失败,对应系统的负责同学要邮件reply all跟进原因。
  4. 关注用例的细节
  团队的负责人要去看用例的质量,而不只是用例的数量和执行情况,比如断言做到什么程度,哪些是写死的哪些参数化了,功能之间的复用情况。其实这个和所有的事情一样,如果真的重要,就应该要跳进去关注细节。
  其实说起来上面没有什么高深的地方,道理也浅显,可能是工作久了会变得更加务实,最怕做了没有用的东西。
  1. 关于技术含量的问题,不纠结。当然,有能力和精力做到技术含量很好。但是没有技术含量但是有价值 > 有技术含量效果不好。
  2. 尽可能的复用好的组件,核心的引擎不一定要自己做,特别是业务测试团队。用开源组件,自己开发特点需求,并把它们粘合起来。
  以上粗略的整理,希望给那些想要做功能自动化,但是又受限于非常快得功能版本节奏,也没有大量测试开发人力的业务测试团队一点参考。





31/3123>






posted @ 2014-07-10 19:23 顺其自然EVO 阅读(313) | 评论 (0)编辑 收藏

如何做好安卓手机的performance测试?

提问:如何做好安卓手机的performance测试?
  回答:任何测试在测试之前都应该建立相应的计划或方案,手机的performance测试同样也不例外,如何做好performance测试我认为就是制定1个适应公司需求的性能测试计划,而好的测试计划就需要包含下面几个方面:
  1.Performance范围的确立,手机常用的模块性能必然需要纳入测试范围,如打开联系人界面的时间、点击拨号按钮到显示拨号UI的时间,这些都是重要的performance测试范围,这些模块的性能会影响到最终用户的使用体验;其次手机一些附件的性能需要考虑是否加入性能测试,如WIFI的吞吐率和距离、蓝牙的吞吐率和距离、数据网络的传输速率等等,这些在定制测试时一般是会有要求的,但由于附件性能是偏硬件的,需根据公司的实际情况,确定是否将其纳入测试范围
  2.Performance目标的确立,我们不可能把被测手机最终的性能结果作为我们的验收结果,这样就失去了性能测试的意义。确定performance的目标是测试重要的1个环节,如果用户提供验收标准,当然是求之不得的,但实际中性能的标准往往是参考某一配置相近的上市机器来确定标准的,这种参考并不等于照搬,由于存在误差,我的经验是在参考机的基础上乘以1.5的系数。
  3.Performance测试方法的确立,安卓手机模块的性能测试有很多方法,最简单的可以用秒表计时,当然这样的误差会较大。如果需要精确一点的可以通过log确定,从打开1个activity的开始时间到结束时间,adb的log都是可以看到的,但这个时间和用户实际操作的时间仍然是有误差的,这个只是程序逻辑上的时间,不等于用户操作时的时间。最贴近用户体验的方法是用精度高的摄像机拍摄视频,然后通过软件拆分视频帧来计算时间,这样的结果是最可靠的。无论才用什么方法,都需要采用多次测试求平均值的方法来减少误差。关于附件性能的测试,一般都是使用相应的工具进行测试。
  4.Performance测试实施,测试范围、测试目标、测试方法确定完毕后,就需要明确性能测试什么时候进行,什么时候结束,什么阶段实现什么目标,最主要的是这些都必须时可衡量的。一般性能测试都是在alpha版本的milestone通过之后开始进行,各个阶段都需要执行1个轮次或多个轮次的性能测试

posted @ 2014-07-10 19:21 顺其自然EVO 阅读(333) | 评论 (0)编辑 收藏

使用EasyMock更轻松地进行测试

测试驱动开发是软件开发的重要部分。如果代码不进行测试,就是不可靠的。所有代码都必须测试,而且理想情况下应该在编写代码之前编写测试。但是,有些东西容易测试,有些东西不容易。如果要编写一个代表货币值的简单的类,那么很容易测试把 $1.23 和 $2.8 相加是否能够得出 $4.03,而不是 $3.03 或 $4.029999998。测试是否不会出现 $7.465 这样的货币值也不太困难。但是,如何测试把 $7.50 转换为 €5.88 的方法呢(尤其是在通过连接数据库查询随时变动的汇率信息的情况下)?在每次运行程序时,amount.toEuros() 的正确结果都可能有变化。
  答案是 mock 对象。测试并不通过连接真正的服务器来获取最新的汇率信息,而是连接一个 mock 服务器,它总是返回相同的汇率。这样就可以得到可预测的结果,可以根据它进行测试。毕竟,测试的目标是 toEuros() 方法中的逻辑,而不是服务器是否发送正确的值。(那是构建服务器的开发人员要操心的事)。这种 mock 对象有时候称为 fake。
  mock 对象还有助于测试错误条件。例如,如果 toEuros() 方法试图获取最新的汇率,但是网络中断了,那么会发生什么?可以把以太网线从计算机上拔出来,然后运行测试,但是编写一个模拟网络故障的 mock 对象省事得多。
  mock 对象还可以测试类的行为。通过把断言放在 mock 代码中,可以检查要测试的代码是否在适当的时候把适当的参数传递给它的协作者。可以通过 mock 查看和测试类的私有部分,而不需要通过不必要的公共方法公开它们。
  最后,mock 对象有助于从测试中消除依赖项。它们使测试更单元化。涉及 mock 对象的测试中的失败很可能是要测试的方法中的失败,不太可能是依赖项中的问题。这有助于隔离问题和简化调试。
  EasyMock 是一个针对 Java 编程语言的开放源码 mock 对象库,可以帮助您快速轻松地创建用于这些用途的 mock 对象。EasyMock 使用动态代理,让您只用一行代码就能够创建任何接口的基本实现。通过添加 EasyMock 类扩展,还可以为类创建 mock。可以针对任何用途配置这些 mock,从方法签名中的简单哑参数到检验一系列方法调用的多调用测试。
  EasyMock 简介
  现在通过一个具体示例演示 EasyMock 的工作方式。清单 1 是虚构的 ExchangeRate 接口。与任何接口一样,接口只说明实例要做什么,而不指定应该怎么做。例如,它并没有指定从 Yahoo 金融服务、政府还是其他地方获取汇率数据。
  清单 1. ExchangeRate
  import java.io.IOException;
  public interface ExchangeRate {
  double getRate(String inputCurrency, String outputCurrency) throws IOException;
  }
  清单 2 是假定的 Currency 类的骨架。它实际上相当复杂,很可能包含 bug。(您不必猜了:确实有 bug,实际上有不少)。
  清单 2. Currency 类
import java.io.IOException;
public class Currency {
private String units;
private long amount;
private int cents;
public Currency(double amount, String code) {
this.units = code;
setAmount(amount);
}
private void setAmount(double amount) {
this.amount = new Double(amount).longValue();
this.cents = (int) ((amount * 100.0) % 100);
}
public Currency toEuros(ExchangeRate converter) {
if ("EUR".equals(units)) return this;
else {
double input = amount + cents/100.0;
double rate;
try {
rate = converter.getRate(units, "EUR");
double output = input * rate;
return new Currency(output, "EUR");
} catch (IOException ex) {
return null;
}
}
}
public boolean equals(Object o) {
if (o instanceof Currency) {
Currency other = (Currency) o;
return this.units.equals(other.units)
&& this.amount == other.amount
&& this.cents == other.cents;
}
return false;
}
public String toString() {
return amount + "." + Math.abs(cents) + " " + units;
}
}
 Currency 类设计的一些重点可能不容易一下子看出来。汇率是从这个类之外 传递进来的,并不是在类内部构造的。因此,很有必要为汇率创建 mock,这样在运行测试时就不需要与真正的汇率服务器通信。这还使客户机应用程序能够使用不同的汇率数据源。
  清单 3 给出一个 JUnit 测试,它检查在汇率为 1.5 的情况下 $2.50 是否会转换为 €3.75。使用 EasyMock 创建一个总是提供值 1.5 的ExchangeRate 对象。
  清单 3. CurrencyTest 类
import junit.framework.TestCase;
import org.easymock.EasyMock;
import java.io.IOException;
public class CurrencyTest extends TestCase {
public void testToEuros() throws IOException {
Currency expected = new Currency(3.75, "EUR");
ExchangeRate mock = EasyMock.createMock(ExchangeRate.class);
EasyMock.expect(mock.getRate("USD", "EUR")).andReturn(1.5);
EasyMock.replay(mock);
Currency actual = testObject.toEuros(mock);
assertEquals(expected, actual);
}
}
  老实说,在我第一次运行 清单 3 时失败了,测试中经常出现这种问题。但是,我已经纠正了 bug。这就是我们采用 TDD 的原因。
  运行这个测试,它通过了。发生了什么?我们来逐行看看这个测试。首先,构造测试对象和预期的结果:
  Currency testObject = new Currency(2.50, "USD");
  Currency expected = new Currency(3.75, "EUR");
  这不是新东西。
  接下来,通过把 ExchangeRate 接口的 Class 对象传递给静态的 EasyMock.createMock() 方法,创建这个接口的 mock 版本:
  ExchangeRate mock = EasyMock.createMock(ExchangeRate.class);
  这是到目前为止最不可思议的部分。注意,我可没有编写实现 ExchangeRate 接口的类。另外,EasyMock.createMock() 方法绝对无法返回ExchangeRate 的实例,它根本不知道这个类型,这个类型是我为本文创建的。即使它能够通过某种奇迹返回 ExchangeRate,但是如果需要模拟另一个接口的实例,又会怎么样呢?
  我最初看到这个时也非常困惑。我不相信这段代码能够编译,但是它确实可以。这里的 “黑魔法” 来自 Java 1.3 中引入的 Java 5 泛型和动态代理(见 参考资料)。幸运的是,您不需要了解它的工作方式(发明这些诀窍的程序员确实非常聪明)。
  下一步同样令人吃惊。为了告诉 mock 期望什么结果,把方法作为参数传递给 EasyMock.expect() 方法。然后调用 andReturn() 指定调用这个方法应该得到什么结果:
  EasyMock.expect(mock.getRate("USD", "EUR")).andReturn(1.5);
  EasyMock 记录这个调用,因此知道以后应该重放什么。
  如果在使用 mock 之前忘了调用 EasyMock.replay(),那么会出现 IllegalStateException 异常和一个没有什么帮助的错误消息:missing behavior definition for the preceding method call。
  接下来,通过调用 EasyMock.replay() 方法,让 mock 准备重放记录的数据:
  EasyMock.replay(mock);
  这是让我比较困惑的设计之一。EasyMock.replay() 不会实际重放 mock。而是重新设置 mock,在下一次调用它的方法时,它将开始重放。
  现在 mock 准备好了,我把它作为参数传递给要测试的方法:
  为类创建 mock
  从实现的角度来看,很难为类创建 mock。不能为类创建动态代理。标准的 EasyMock 框架不支持类的 mock。但是,EasyMock 类扩展使用字节码操作产生相同的效果。您的代码中采用的模式几乎完全一样。只需导入org.easymock.classextension.EasyMock 而不是org.easymock.EasyMock。为类创建 mock 允许把类中的一部分方法替换为 mock,而其他方法保持不变。
  Currency actual = testObject.toEuros(mock);
  最后,检查结果是否符合预期:
  assertEquals(expected, actual);
  这就完成了。如果有一个需要返回特定值的接口需要测试,就可以快速地创建一个 mock。这确实很容易。ExchangeRate 接口很小很简单,很容易为它手工编写 mock 类。但是,接口越大越复杂,就越难为每个单元测试编写单独的 mock。通过使用 EasyMock,只需一行代码就能够创建 java.sql.ResultSet 或 org.xml.sax.ContentHandler 这样的大型接口的实现,然后向它们提供运行测试所需的行为。
  测试异常
  mock 最常见的用途之一是测试异常条件。例如,无法简便地根据需要制造网络故障,但是可以创建模拟网络故障的 mock。
  当 getRate() 抛出 IOException 时,Currency 类应该返回 null。清单 4 测试这一点:
  清单 4. 测试方法是否抛出正确的异常
public void testExchangeRateServerUnavailable() throws IOException {
ExchangeRate mock = EasyMock.createMock(ExchangeRate.class);
EasyMock.expect(mock.getRate("USD", "EUR")).andThrow(new IOException());
EasyMock.replay(mock);
Currency actual = testObject.toEuros(mock);
assertNull(actual);
}
  这里的新东西是 andThrow() 方法。顾名思义,它只是让 getRate() 方法在被调用时抛出指定的异常。
  可以抛出您需要的任何类型的异常(已检查、运行时或错误),只要方法签名支持它即可。这对于测试极其少见的条件(例如内存耗尽错误或无法找到类定义)或表示虚拟机 bug 的条件(比如 UTF-8 字符编码不可用)尤其有帮助。
  设置预期
  EasyMock 不只是能够用固定的结果响应固定的输入。它还可以检查输入是否符合预期。例如,假设 toEuros() 方法有一个 bug(见清单 5),它返回以欧元为单位的结果,但是获取的是加拿大元的汇率。这会让客户发一笔意外之财或遭受重大损失。
  清单 5. 有 bug 的 toEuros() 方法
public Currency toEuros(ExchangeRate converter) {
if ("EUR".equals(units)) return this;
else {
double input = amount + cents/100.0;
double rate;
try {
rate = converter.getRate(units, "CAD");
double output = input * rate;
return new Currency(output, "EUR");
} catch (IOException e) {
return null;
}
}
}
  但是,不需要为此编写另一个测试。清单 4 中的 testToEuros 能够捕捉到这个 bug。当对这段代码运行清单 4 中的测试时,测试会失败并显示以下错误消息:
  "java.lang.AssertionError:
  Unexpected method call getRate("USD", "CAD"):
  getRate("USD", "EUR"): expected: 1, actual: 0".
  注意,这并不是我设置的断言。EasyMock 注意到我传递的参数不符合测试用例。
  在默认情况下,EasyMock 只允许测试用例用指定的参数调用指定的方法。但是,有时候这有点儿太严格了,所以有办法放宽这一限制。例如,假设希望允许把任何字符串传递给 getRate() 方法,而不仅限于 USD 和 EUR。那么,可以指定 EasyMock.anyObject() 而不是显式的字符串,如下所示:
  EasyMock.expect(mock.getRate(
  (String) EasyMock.anyObject(),
  (String) EasyMock.anyObject())).andReturn(1.5);
  还可以更挑剔一点儿,通过指定 EasyMock.notNull() 只允许非 null 字符串:
  EasyMock.expect(mock.getRate(
  (String) EasyMock.notNull(),
  (String) EasyMock.notNull())).andReturn(1.5);
  静态类型检查会防止把非 String 对象传递给这个方法。但是,现在允许传递 USD 和 EUR 之外的其他 String。还可以通过EasyMock.matches() 使用更显式的正则表达式。下面指定需要一个三字母的大写 ASCII String:
  EasyMock.expect(mock.getRate(
  (String) EasyMock.matches("[A-Z][A-Z][A-Z]"),
  (String) EasyMock.matches("[A-Z][A-Z][A-Z]"))).andReturn(1.5);
  使用 EasyMock.find() 而不是 EasyMock.matches(),就可以接受任何包含三字母大写子 String 的 String。 EasyMock 为基本数据类型提供相似的方法:
  EasyMock.anyInt()
  EasyMock.anyShort()
  EasyMock.anyByte()
  EasyMock.anyLong()
  EasyMock.anyFloat()
  EasyMock.anyDouble()
  EasyMock.anyBoolean()
  对于数字类型,还可以使用 EasyMock.lt(x) 接受小于 x 的任何值,或使用 EasyMock.gt(x) 接受大于 x 的任何值。
  在检查一系列预期时,可以捕捉一个方法调用的结果或参数,然后与传递给另一个方法调用的值进行比较。最后,通过定义定制的匹配器,可以检查参数的任何细节,但是这个过程比较复杂。但是,对于大多数测试,EasyMock.anyInt()、EasyMock.matches() 和 EasyMock.eq() 这样的基本匹配器已经足够了。
  严格的 mock 和次序检查
  EasyMock 不仅能够检查是否用正确的参数调用预期的方法。它还可以检查是否以正确的次序调用这些方法,而且只调用了这些方法。在默认情况下,不执行这种检查。要想启用它,应该在测试方法末尾调用 EasyMock.verify(mock)。例如,如果 toEuros() 方法不只一次调用getRate(),清单 6 就会失败。
  清单 6. 检查是否只调用 getRate() 一次
public void testToEuros() throws IOException {
Currency expected = new Currency(3.75, "EUR");
ExchangeRate mock = EasyMock.createMock(ExchangeRate.class);
EasyMock.expect(mock.getRate("USD", "EUR")).andReturn(1.5);
EasyMock.replay(mock);
Currency actual = testObject.toEuros(mock);
assertEquals(expected, actual);
EasyMock.verify(mock);
}
  EasyMock.verify() 究竟做哪些检查取决于它采用的操作模式:
  Normal — EasyMock.createMock():必须用指定的参数调用所有预期的方法。但是,不考虑调用这些方法的次序。调用未预期的方法会导致测试失败。
  Strict — EasyMock.createStrictMock():必须以指定的次序用预期的参数调用所有预期的方法。调用未预期的方法会导致测试失败。
  Nice — EasyMock.createNiceMock():必须以任意次序用指定的参数调用所有预期的方法。调用未预期的方法不会 导致测试失败。Nice mock 为没有显式地提供 mock 的方法提供合理的默认值。返回数字的方法返回 0,返回布尔值的方法返回 false。返回对象的方法返回 null。
  检查调用方法的次序和次数对于大型接口和大型测试更有意义。例如,请考虑 org.xml.sax.ContentHandler 接口。如果要测试一个 XML 解析器,希望输入文档并检查解析器是否以正确的次序调用 ContentHandler 中正确的方法。例如,请考虑清单 7 中的简单 XML 文档:
  清单 7. 简单的 XML 文档
  <root>
  Hello World!
  </root>
  根据 SAX 规范,在解析器解析文档时,它应该按以下次序调用这些方法:
  setDocumentLocator()
  startDocument()
  startElement()
  characters()
  endElement()
  endDocument()
  但是,更有意思的是,对 setDocumentLocator() 的调用是可选的;解析器可以多次调用 characters()。它们不需要在一次调用中传递尽可能多的连续文本,实际上大多数解析器不这么做。即使是对于清单 7 这样的简单文档,也很难用传统的方法测试 XML 解析器,但是 EasyMock 大大简化了这个任务,见清单 8:
  清单 8. 测试 XML 解析器
import java.io.*;
import org.easymock.EasyMock;
import org.xml.sax.*;
import org.xml.sax.helpers.XMLReaderFactory;
import junit.framework.TestCase;
public class XMLParserTest extends TestCase {
private  XMLReader parser;
protected void setUp() throws Exception {
parser = XMLReaderFactory.createXMLReader();
}
public void testSimpleDoc() throws IOException, SAXException {
String doc = "<root>\n  Hello World!\n</root>";
ContentHandler mock = EasyMock.createStrictMock(ContentHandler.class);
mock.setDocumentLocator((Locator) EasyMock.anyObject());
EasyMock.expectLastCall().times(0, 1);
mock.startDocument();
mock.startElement(EasyMock.eq(""), EasyMock.eq("root"), EasyMock.eq("root"),
(Attributes) EasyMock.anyObject());
mock.characters((char[]) EasyMock.anyObject(),
EasyMock.anyInt(), EasyMock.anyInt());
EasyMock.expectLastCall().atLeastOnce();
mock.endElement(EasyMock.eq(""), EasyMock.eq("root"), EasyMock.eq("root"));
mock.endDocument();
EasyMock.replay(mock);
parser.setContentHandler(mock);
InputStream in = new ByteArrayInputStream(doc.getBytes("UTF-8"));
parser.parse(new InputSource(in));
EasyMock.verify(mock);
}
}
  这个测试展示了几种新技巧。首先,它使用一个 strict mock,因此要求符合指定的次序。例如,不希望解析器在调用 startDocument() 之前调用 endDocument()。
  第二,要测试的所有方法都返回 void。这意味着不能把它们作为参数传递给 EasyMock.expect()(就像对 getRate() 所做的)。(EasyMock 在许多方面能够 “欺骗” 编译器,但是还不足以让编译器相信 void 是有效的参数类型)。因此,要在 mock 上调用 void 方法,由 EasyMock 捕捉结果。如果需要修改预期的细节,那么在调用 mock 方法之后立即调用 EasyMock.expectLastCall()。另外注意,不能作为预期参数传递任何 String、int 和数组。必须先用 EasyMock.eq() 包装它们,这样才能在预期中捕捉它们的值。
  清单 8 使用 EasyMock.expectLastCall() 调整预期的方法调用次数。在默认情况下,预期的方法调用次数是一次。但是,我通过调用.times(0, 1) 把 setDocumentLocator() 设置为可选的。这指定调用此方法的次数必须是零次或一次。当然,可以根据需要把预期的方法调用次数设置为任何范围,比如 1-10 次、3-30 次。对于 characters(),我实际上不知道将调用它多少次,但是知道必须至少调用一次,所以对它使用 .atLeastOnce()。如果这是非 void 方法,就可以对预期直接应用 times(0, 1) 和 atLeastOnce()。但是,因为这些方法返回 void,所以必须通过 EasyMock.expectLastCall() 设置它们。
  最后注意,这里对 characters() 的参数使用了 EasyMock.anyObject() 和 EasyMock.anyInt()。这考虑到了解析器向 ContentHandler 传递文本的各种方式。
  mock 和真实性
  有必要使用 EasyMock 吗?其实,手工编写的 mock 类也能够实现 EasyMock 的功能,但是手工编写的类只能适用于某些项目。例如,对于 清单 3,手工编写一个使用匿名内部类的 mock 也很容易,代码很紧凑,对于不熟悉 EasyMock 的开发人员可读性可能更好。但是,它是一个专门为本文构造的简单示例。在为 org.w3c.dom.Node(25 个方法)或 java.sql.ResultSet(139 个方法而且还在增加)这样的大型接口创建 mock 时,EasyMock 能够大大节省时间,以最低的成本创建更短更可读的代码。
  最后,提出一条警告:使用 mock 对象可能做得太过分。可能把太多的东西替换为 mock,导致即使在代码质量很差的情况下,测试仍然总是能够通过。替换为 mock 的东西越多,接受测试的东西就越少。依赖库以及方法与其调用的方法之间的交互中可能存在许多 bug。把依赖项替换为 mock 会隐藏许多实际上可能发现的 bug。在任何情况下,mock 都不应该是您的第一选择。如果能够使用真实的依赖项,就应该这么做。mock 是真实类的粗糙的替代品。但是,如果由于某种原因无法用真实的类可靠且自动地进行测试,那么用 mock 进行测试肯定比根本不测试强。

posted @ 2014-07-10 19:20 顺其自然EVO 阅读(3420) | 评论 (0)编辑 收藏

仅列出标题
共394页: First 上一页 85 86 87 88 89 90 91 92 93 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜