所有的
编程语言我都讨厌。曾经我想自创一门语言,但我没搞明白到底需要一门什么语言,所以也从未开始过。 许多时候,你没法选择使用哪种语言。不管我在用哪种语言,我都尝试去接受它的优点和缺点。
Java
喜欢Java的人肯定喜欢打字。我指的就是敲打键盘上的键。你得不断地重复又重复。
设计Java系统的人是个疯子,他解决问题的方式就是,设计模式。如果你把设计模式看作是这个语言中解决问题的一种方式,那么你会发现Java里有许多这样的设计模式。
另一方面,Sun的这些家伙的确是费了点心思在Java规范上的,这使得它能运行在嵌入式系统上,所以这块我们还是坚持在使用它。我很难相信
Python或者C在我的
手机桌面系统上运行。
还有,那些个目录又是怎么回事?我必须得使用Eclipse,因为只有它知道怎么跳过那1000个字长的路径名。如果我在应用的同一个目录下放10个类,会不会 伤害到某些人?
C
C是精确的。当我用C写程序的时候,如果搞定了,我知道它是靠谱的。它就像是用一把小刷子在画一幅巨作。在这么详细的层面上写代码需要一种不同的心态。当你坐下来写C的时候,在动手之前你就得规划好到底怎么写。否则后面肯定得费很多工夫去改。
如果你的经验足够丰富,内存泄露这种事就不太会找上门。它的第二特性——malloc/free总是形影不离。你不能忘了任何一个。否则就像是忘了冲水或者关灯。你就这么做就是了。
有句话说得好,如果你打算给房子上漆,一把好刷子可远远不够。我猜你肯定想要个大滚轴。如果让我写一整个应用或者系统,能不用C的话我肯定不用。
C程序想要进行改动可得费老劲了。当我写算法的时候,我知道第一遍肯定是不会对的,所以我通常都先用Python写,搞定了之后再翻译成C的。
C++
它就是个有string类的C。同时还有数组,列表,队列等东西,你可以用它们来实现你想要的。一言以蔽之:别想着自创新模板。这太困难了。除了这个,C++还改良了一下C,用C++你可以写出非常不错的软件。它这个额外的特性使得它可以用于一些大型系统上,只要大家都还遵循同样的约束的话,难度还不算太大。
JavaScript
这是个没人喜欢的语言。不过它喜欢你。当你刚开始
学习它的时候,你可能会写出一些非常糟糕的代码,把对象用作字典,别的对象作KEY,不过这样也是OK的,因为这些代码运行起来也没有什么问题,只要浏览器还支持JavaScript就好。
JavaScript没有连接器,因此所有的代码都共享一个命名空间,不过还好大家都知道这一点,所以还能一起和谐相处。
CoffeeScirpt
CoffeeScirpt是一个解释器,它将那些长得像
Ruby的奇怪的语言逐行地翻译成JavaScript。它是一个拥有所有外来语法的JavaScript——括号,方括号,额外关键字移除。只有代码的基本含义还保留着。
CoffeeScirpt挺不错的。如果你要写很多代码的时候,它能让你提高至少25%的效率。你可以一次在屏幕上看到更多行的代码。
当你用CoffeeScript写代码的时候,你得时刻记住这是要生成JavaScript的。问题就在这。你得先去学习JavaScript。项目来的新人都得先学JavaScript,然后才能学CoffeeScript,最后才能去学习项目代码。
node.js
我也希望能爱上它。我觉得我给过它机会了。它的回调让我无法忍受。我知道会有这么一天,因为某个原因,其中一个回调并没有出现,然后我的应用就会堵在那一直等待。真是要了命了。
还有一点就是,它几乎没有内建任何东西。如果你要做某件事情,总是会有一大堆模块来实现这个功能的。该选哪个呢?如果出现问题了,哪个模块会有人来支持?
Scala
Scala是一门函数式,强类型的语言,它会编译成JVM代码。
我是在
工作中学的Scala。有一家初创公司的生产系统用的是它,我是在后期才加入他们的。
这让我看到了Scala丑陋的一面:类型推导。类型推荐被它用到了极致。每个对象都有类型,不过想确定它是什么类型的,你得检查不同分层上的好几个文件才行。Scala也继承了Java的文件夹的坏毛病,因此你要查找某个类型的话得进入好几层目录才能找到对应的那个文件。
简而言之,Scala是极好的——对于那些最初的开发人员而言。新加入的成员为了熟悉现有的代码,得有一个很长的学习曲线。
Erlang
Erlang也是我曾经想爱上的一位。我真的努力了。它是一门美丽的函数式语言,它可以写出很精致的小模块,它们以一种精确的方式进行通信,你的系统可以运行10年以上,因为它能处理未知问题,如果必要的话还会重启,然后继续运行。
不过它的结构太复杂了。开发似乎要停留在伯克利发明socket的那个年代。当前时代所需的东西几乎一样都没有。为什么开发一个简单的WEB服务需要费这么大的工夫?
Go
Go很容易学习,对于新人而言也是如此。它使用40年前的语言概念来构建一个健壮的异步系统,但它让你能像写同步代码一样编程。你可以不费吹灰之力写出1000个可以安全工作的线程。
在库支持方面它仍需要改进。当我想做某事的时候,该用哪个库——github上2011年的那个,还是2013年开始的那个半成品?一个是官方主页链接的,不过它的官方主页看起来并不是最新的。好吧,我觉得我还是自己写一个吧。。。
还有,为什么追加元素到数组里也这么费劲?
Python
在Python里,不管你想做什么都会有一个对应的库,如果你用的是Linux,它绝对是不二选择,因为它可以一键安装。
如果你想做些数字处理或者科学运算,选择Python吧,你值得拥有。
Python中的字符串即可能是文本的也可能是二进制的,因此你得上来就学习下文本编码的东东。
Python 3
Python 3和Python有许多共同的特性,不过它却是门不同的语言。由于它比较新,因此支持的并不是很好。我也想使用它,不过总会有那么一个库,它是只支持Python 2的。
虽然很早之前就成功装过这两个软件了。但是前阵子重装了系统再装这两个软件时却发现我又把破解的方法给忘了。后来从历史文档中搜索了好久才得到解决。想想这些还是需要总结,事情多了,难免忘记。也分享给需要的童鞋们。
LR11的破解方法
1)退出程序,把下载文件中的lm70.dll和mlr5lprg.dll覆盖掉..\HP\LoadRunner\bin下的这两个文件
2)注意,win7的话一定要以管理员身份运行启动程序,启动后,点击 configuration->loadrunner license,此时可能会有两个许可证信息存在,退出程序,点击deletelicense.exe文件,来删除刚才得许可证信息(即时原来没有lisense最好也运行一下)
3)再次打开程序, configuration->loadrunner license->new license,在弹出的输入框中输入license序列号AEABEXFR-YTIEKEKJJMFKEKEKWBRAUNQJU-KBYGB,点击确定,验证通过后,则破解成功!
QTP11的破解方法
1)安装成功后,手工创建:C:\Program Files\Common Files\Mercury Interactive下,
建立文件夹License Manager(这个很重要,必须要创建)
2)拷贝破解文件mgn-mqt82.exe至第一步创建的目录下。
3)运行破解文件,提示在C:\Program Files\Common Files\Mercury Interactive下创建了lservrc文件
4)用记事本打开lservrc,拷贝第一个#号前的一大串字符串。运行
QTP,输入这串字符
5)安装QTP的
功能测试许可服务器安装程序(打开qtp11的安装包里边的)
6)删掉SafeNet Sentinel目录(C:\ProgramData\SafeNet Sentinel)
7)运行\HP\QuickTest Professional\bin中的instdemo.exe
通过一定的工具结合相应的测试方法,对部署的系统应用进行测试,发现系统应用内部存在的代码逻辑问题及应用部署的机器硬件资源瓶颈问题及应用部署架构存在架构错误问题,如:网络端、客户端、服务端搭建的架构问题;
负载测试:是一个分析软件应用程序和支撑架构、模拟真实环境的使用,从而来确定能够接收的性能过;
压力测试(Stress Testing):是通过确定一个系统的瓶颈或者不能接收的性能点,来获得系统能提供的最大服务级别的测试;
性能测试的目的:
性能测试的目的主要体现在三个方面:以真实的业务为依据,选择有代表性的、关键的业务操作设计测试案例,以评价系统的当前性能;当扩展应用程序的功能或者新的应用程序将要被部署时,负载测试会帮助确定系统是否还能够处理期望的用户负载,以预测系统的未来性能;通过模拟成百上千个用户,重复执行和运行测试,可以确认性能瓶颈并优化和调整应用,目的在于寻找到瓶颈问题;
项目开发周期:初始时刻,项目更多关注的是功能实现,此时
功能测试显得尤为重要,测试的提前介入,可以提前预测风险,减少项目开发周期、节约开发成本;功能测试后的阶段,个人认为应该是性能测试(试想,如果一个项目连功能都实现不了,更何谈性能测试);在功能完毕之后,引入性能测试,通过性能测试对开发项目潜在的问题进行排查(功能测试,仅仅是几个人或者几十个人简单的对应用功能的一个测试,对于应用真正上线后的大量用户使用,应用存在的潜在风险,并不能做很好的预估,尤其是当前空前的竞争压力下,应用上线后的失败,很可能导致整个项目的失败;例如:12306订票网站,使用量之大,可能全世界前所未有,调动全国人力去测试应用性能问题,肯定是不可能的。如果事先不经过性能测试,贸然上线,在如此之多的用户使用情况下,系统崩溃将是怎样的一种后果。);
案例分享:编者曾经从事过一个项目,伴随项目的始终。前期阶段,由于测试提前介入,以及项目开发采用的
敏捷开发方式,项目很快在不到半年的时间内,功能近乎完美完成。项目经理本着稳妥起见,引入性能测试,对项目潜在的风险进行评估,然后就搭建了一套模拟环境,专用于性能测试,搭建的模拟环境30用户并发运行,项目一点问题没有,进一步提升并发用户数,各种问题接踵而来;经过系统调优后(发布的应用系统参数等),部分问题解决;为了进一步测试实际情况下存在问题,性能测试环境由模拟环境切到了生产环境上,此时是大量用户下的并发,部分业务是没有问题的,但是更多的问题是集中在涉及到
工作流的一些业务场景上,后台日志各种报错;通过抓取后台日志,对问题进行定位分析,很快排查解决了代码开发中存在的一些逻辑问题;代码修复后重新上线,问题已基本不存在了;项目也很快结束,大大的缩短了项目开发周期、节约了开发成功、更好的适用于用户;
性能测试注意点:
录制脚本尽量模拟实际用户操作,在场景设计时,尽量与实际场景一致,对于用户使用比较多的业务,应着重关注;
性能测试尽可能在实际生产环境上进行,普通模拟环境并不能真正发现实际生产环境下,应用存在的问题,但是并非弃用模拟环境;
性能测试,对于应用系统部署的环境上,可能需要部署一些系统性能监控软件,在软件的选取上,尽可能降低软件自身运行对系统性能的影响;
性能测试,特别是应用与
数据库交互的业务操作上,需要提前预制符合性能测试业务需求的数据,在此基础上,尽量让环境测试环境可多次重复使用,这就要求数据、应用可还原;
性能测试技能掌握要求:
测试环境搭建,环境搭建不仅仅是性能测试所需要具备的技能,也是测试人员所需要具备的基本技能;很多测试,包括应用的安装卸载,都需要测试人员具备这一技能;
应用搭建使用协议的了解,很多情况下,性能测试人员需要录制测试脚本,这就要测试人员对应用采用的协议有充分的了解;
服务器架构的了解,单一的一台服务器、多台服务器情况下的集群架构等,了解服务器架构,可以为性能测试人员初期性能调优提供帮助;
操作系统机能的掌握,特别是
Linux操作系统的了解,当前大多数的应用部署在Linux操作系统之上,性能测试人员需要掌握操作系统知识这一基础技能;
数据库知识,面对大数据时代,数据库机能的掌握不仅仅可以为性能测试服务,还可以为你今后的华丽转型,提供良好保障,华丽的DBA;
良好的编码思量。基础的编码知识,对于编码的了解,可以为你今后冲击高级性能测试工程师提供有力保障,一个高级性能测试工程师,应当具有性能调优这一技能,编码就显得尤为重要;
对于新技术、新思想的一种追求与掌握;
1、数据库连接jdbc.properties配置详解 jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
jdbc.driver=不同的数据库厂商驱动,此处不一一列举
接下来,详细配置代码如下:
<beans> <!-- picks up and registers AppConfig as a bean definition --> <context:component-scan base-package="com.acme"/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans> |
DBCP连接池
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> C3P0 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <context:property-placeholder location="jdbc.properties"/> proxool <bean id="dataSource" class="org.logicalcobwebs.proxool.ProxoolDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <context:property-placeholder location="jdbc.properties"/> |
当然还有Druid 、DBPool 、Jakarta DBCP 等
一、多路径解释 多路径,顾名思义就是有多种选择的路径。在SAN或IPSAN环境,主机和存储之间外加了光纤交换机,这就导致主机和存储之间交换速度和效率增强,一条路径肯定是不行的,也是不安全不稳定的。多路径就是要来解决从主机到磁盘之间最快,最高效的问题。主要实现如下几个功能
故障的切换和恢复
IO流量的负载均衡
磁盘的虚拟化
多路径之前一直是存储厂商负责解决,竟来被拆分出来单独卖钱了。
构架基本是这样的:存储,多路径软件,光纤交换机,主机,主机系统。
二、LINUX下的multipath
1、查看是否自带安装?
[root@web2 multipath]# rpm -qa|grep device
device-mapper-1.02.39-1.el5
device-mapper-1.02.39-1.el5
device-mapper-multipath-0.4.7-34.el5
device-mapper-event-1.02.39-1.el5
[root@web2 multipath]#
2、安装
rpm -ivh device-mapper-1.02.39-1.el5.rpm #安装映射包
rpm -ivh device-mapper-multipath-0.4.7-34.el5.rpm #安装多路径包
外加加入开机启动
chkconfig –level 2345 multipathd on #设置成开机自启动multipathd
lsmod |grep dm_multipath #来检查安装是否正常
3、配置
# on the default devices. blacklist { devnode "^(ram|raw|loop|fd|md|dm-|sr|sr|scd|st)[0-9]*" devnode "^hd[a-z]" } devices { device { vendor "HP" path_grouping_policy multibus features "1 queue_if_no_path" path_checker readsector() failback immediate } }<br><br>完整的配置如下: blacklist { devnode "^sda" } defaults { user_friendly_names no } multipaths { multipath { wwid 14945540000000000a67854c6270b4359c66c272e2f356321 alias iscsi-dm0 path_grouping_policy multibus path_checker tur path_selector "round-robin 0" } multipath { wwid 14945540000000000dcca2eda91d70b81edbcfce2357f99ee alias iscsi-dm1 path_grouping_policy multibus path_checker tur path_selector "round-robin 0" } multipath { wwid 1494554000000000020f763489c165561101813333957ed96 alias iscsi-dm2 path_grouping_policy multibus path_checker tur path_selector "round-robin 0" } multipath { wwid 14945540000000000919ca813020a195422ba3663e1f03cc3 alias iscsi-dm3 path_grouping_policy multibus path_checker tur path_selector "round-robin 0" } } devices { device { vendor "iSCSI-Enterprise" product "Virtual disk" path_grouping_policy multibus getuid_callout "/sbin/scsi_id -g -u -s /block/%n" path_checker readsector0 path_selector "round-robin 0" } } |
4、命令 [root@web2 ~]# multipath -h multipath-tools v0.4.7 (03/12, 2006) Usage: multipath [-v level] [-d] [-h|-l|-ll|-f|-F|-r] [-p failover|multibus|group_by_serial|group_by_prio] [device] -v level verbosity level 0 no output 1 print created devmap names only 2 default verbosity 3 print debug information -h print this usage text -b file bindings file location -d dry run, do not create or update devmaps -l show multipath topology (sysfs and DM info) -ll show multipath topology (maximum info) -f flush a multipath device map -F flush all multipath device maps -r force devmap reload -p policy force all maps to specified policy : failover 1 path per priority group multibus all paths in 1 priority group group_by_serial 1 priority group per serial group_by_prio 1 priority group per priority lvl group_by_node_name 1 priority group per target node device limit scope to the device's multipath (udev-style $DEVNAME reference, eg /dev/sdb or major:minor or a device map name) [root@web2 ~]# |
5、启动关闭
# /etc/init.d/multipathd start #开启mulitipath服务
service multipath start
service multipath restart
service multipath shutdown
6、如何获取wwid
1、
[root@vxfs01 ~]# cat /var/lib/multipath/bindings
# Multipath bindings, Version : 1.0
# NOTE: this file is automatically maintained by the multipath program.
# You should not need to edit this file in normal circumstances.
#
# Format:
# alias wwid
#
mpath0 36006016051d50e0035744871c912de11
mpath1 36006016051d50e0034744871c912de11
mpath2 36006016051d50e0032744871c912de11
mpath3 36006016051d50e0039744871c912de11
mpath4 36006016051d50e003a744871c912de11
2、
[root@vxfs01 ~]# multipath -v3 |grep 3600
sdb: uid = 36006016051d50e003a744871c912de11 (callout)
sdc: uid = 36006016051d50e003a744871c912de11 (callout)
sdd: uid = 36006016051d50e003a744871c912de11 (callout)
sde: uid = 36006016051d50e003a744871c912de11 (callout)
36006016051d50e003a744871c912de11 1:0:0:0 sdb 8:16 0 [undef][ready] DGC,RAI
36006016051d50e003a744871c912de11 1:0:1:0 sdc 8:32 1 [undef][ready] DGC,RAI
36006016051d50e003a744871c912de11 2:0:0:0 sdd 8:48 1 [undef][ready] DGC,RAI
36006016051d50e003a744871c912de11 2:0:1:0 sde 8:64 0 [undef][ready] DGC,RAI
Found matching wwid [36006016051d50e003a744871c912de11] in bindings file.
Java的访问控制权限相比于C++等语言可能稍微复杂一点,不过也不难理解。Java的访问控制权限分为两块——“类或接口的访问控制权限”与“变量和方法的访问控制权限”。
1.类或接口的访问控制权限
类或接口的访问控制权限是指能不能用该类建立对象,接口能不能被实现等等。能够修饰类或接口的访问控制权限的修饰符(modifier)只有两个——public和friendly.不过,值得一说的是friendly并不是Java的关键字,它只是一个习惯叫法,指的是“没有写访问控制权限修饰符”的情况。
public修饰的类或接口在同一个包中的任何一个类中都可以被访问。不同包呢?当然能访问啦,否则引包机制不就失效了嘛(因为引包相当于拿到了一个包的public类或接口)。
friendly修饰的类或接口在同一个包中的任何一个类中都可以被访问(和public相同),不能被不同包中的类访问。
总结:类或接口的访问控制权限分为“包中”和“包外”。无论修饰符(modifier)是什么,在“包中”均可访问。对于“包外”,public修饰的类或接口可以被访问,friendly修饰的类或接口不能被访问。
2.变量和方法的访问控制权限
变量和方法的访问控制权限的修饰符(modifier)有四个——public,protected,friendly和private.
变量和方法在本类(定义该变量或方法的类)中不论访问控制权限修饰符是什么,均可被访问(这里先不考虑“静态”的情况)。那么接下来只研究类外。类外也分“包内”和“包外”,接下来就从这两方面说起,并且研究“包外”时只考虑引入的包中的public类,因为friendly的类连直接被访问都做不到,何谈访问变量和方法。
public修饰的变量和方法在“包内”和“包外”均可被访问。
protected修饰的变量和方法在“包内”可以被访问,在“包外”只能被子类访问。
friendly修饰的变量和方法在“包内”可以被访问,在“包外”不能被访问。
private修饰的变量和方法在“包内”或“包外”均不能被访问。
总结:对于“类外”,public,protected,friendly和private的严格性逐渐递增。public可以说没限制,protected剥夺了“包外”非子类的访问能力,friendly在protected基础上进一步剥夺了“包外”子类的访问能力,至此“包外”的访问能力全无;private更严格,它在friendly基础上更是一下剥夺了“包内”的访问能力。
总的来说,看某个成员能否被访问要分两步:1.根据所在类的访问控制权限看该类能否被访问;2.根据该成员的访问控制权限判断取得所在类后该成员能否被访问。
文档需要全面,实时更新,并且易懂。我说的全面是指除了介绍程序的功能外还应该覆盖到代码中一些重要的地方。对很多人来说文档的重要性不言而喻,但很难保持它的及时性和准确性。糟糕的文档的后果通常会浪费更多的资源和时间。往往都是出于一些错误的原因而编写的文档。
要求文档的一些原因
有很多原因导致我们需要编写文档。团队经常会由于一些制度上的要求而编写文档,或者就是纯粹出于无知。下面是一些编写文档的错误的理由:
有人认为文档和项目的成败息息相关。
文档能够证明某些人的存在。
需求方除了文档也不知道要什么好
要你提供文档的人也就是求个安心,知道事情都OK了
文档都是过时的
软件文档的一个主要的问题就是它通常都不是最新的。代码的某个部分可能发生了改动,但是文档却体现不出这个情况。这句话适用于几乎所有的文档,影响最大的其实还是需求和
测试用例。不管你多努力,文档的过期无可避免。
文档对谁有用?
取决于不同的受众,文档的类型和格式也会相应地有所不同。开发人员,测试人员,客户,主管,最终用户都是文档的最大的潜在用户。
开发人员
开发人员不应该依赖于文档,因为它们通常都是过时的。除此之外,没有什么文档能比代码本身更能提供详细以及最新的信息了。如果你想知道某个方法做了些什么,看下这个方法吧。不确定某个类是干嘛的?看一眼它。通常只有代码写的太差了才需要给它添加文档。
使用代码本身作为文档,这并不代表不需要其它的文档了。关键是要避免冗余。如果看一下代码就能获取到系统的详细信息,那么还可以有一些其它的文档来提供快速导读以及更高层面的一个概述的功能。代码本身的文档是回答不了这个系统是干嘛的或者这个系统用到了什么技术啊这种类型的问题。大多数情况下,对于开发人员而言,一个简单的README.md就足够他快速入门的了。像项目描述,环境配置,安装,构建及打包指令这些东西对项目的新成员来说非常有用。但那之后,代码就是你的圣经。产品代码提供了所有需要的详细信息,而测试代码则是作为产品代码的内在意图的一个描述。测试用例就是可执行的文档,而TDD(测试驱动开发)就是实现它的最常见的方式。
假设你用了某种持续集成的方式,如果测试-文档(这里测试就是文档,文档也是测试)中有一部分不对了,这个用例会执行失败,它将会很快得到修复。持续集成解决了测试-文档不正确的问题,不过它不能保证所有功能都是有文档的。由于这个原因(当然也有其它原因)测试-文档应当用TDD的方式来创建。如果在代码开发前,所有的功能都定义成测试用例,那么测试用例就能作为开发人员的一个完备的最新的文档了。
那团队的其它成员怎么办?测试人员,客户,主管,还有其它非码农呢,他们可能无法从产品和测试的代码中获取到所需要的信息。
测试人员
最常见的两种测试就是
黑盒测试和
白盒测试。这个区分很重要,因为它将测试人员也分成了两类,一拨是知道怎么写代码的,至少是能读懂代码的(白盒测试),另一拨是不懂代码的(黑盒测试)。有的时候测试人员也两样都干。不过一般而言,测试都是不懂代码的,因此对开发人员有用的文档对他们来说是没意义的。如果说要从代码中剥离出文档的话,
单元测试可不是什么合适的东西。这就是BDD(行为驱动开发,Behavior Driven Development)存在的价值了。它能为非开发人员提供所需的文档,但同时还兼备TDD和自动化的优点。
客户
客户需要能够给系统增加新的功能,同时他们也需要获取到关于当前系统的重要信息。给他们的文档可不能太技术了(代码当然不行),同时也得是最新的。行为驱动开发(BDD,Behavior Driven Development)的故事和场景应该是提供这类文档的最佳方式了。它能够作为验收标准(在代码开发前),还可以反复的执行,同时还能用自然语言编写,这使得BDD不仅仅能够保证文档是最新的,同时它对那些不想看代码的人来说也非常有用。
可执行的文档
文档是软件不可或缺的一部分。正如软件的其它部分一样,它也得经常进行测试,这样才能保证它是准确的并且是最新的。实现这个最有效的方法就是将这个可执行的文档能够集成到你的持续集成系统里面。TDD是这个方向的不二选择。从较低层面来看的话,单元测试就非常适合作为这个文档。另一方面来说的话,在功能层面来说BDD是一个很好的方式,它可以使用自然语言来进行描述,这保证了文档的可读性。
XMLHttpRequest Level 2 添加了一个新的接口——FormData。利用 FormData 对象,我们可以通过 JavaScript 用一些键值对来模拟一系列表单控件,我们还可以使用 XMLHttpRequest 的 send() 方法来异步的提交表单。与普通的 Ajax 相比,使用 FormData 的最大优点就是我们可以异步上传二进制文件。
创建一个FormData对象
你可以先创建一个空的 FormData 对象,然后使用 append() 方法向该对象里添加字段,如下:
var oMyForm = new FormData(); oMyForm.append("username", "Groucho"); oMyForm.append("accountnum", 123456); // 数字123456被立即转换成字符串"123456" // fileInputElement中已经包含了用户所选择的文件 oMyForm.append("userfile", fileInputElement.files[0]); var oFileBody = "<a id="a"><b id="b">hey!</b></a>"; // Blob对象包含的文件内容 var oBlob = new Blob([oFileBody], { type: "text/xml"}); oMyForm.append("webmasterfile", oBlob); var oReq = new XMLHttpRequest(); oReq.open("POST", "http://foo.com/submitform.php"); oReq.send(oMyForm); |
注:字段 "userfile" 和 "webmasterfile" 的值都包含了一个文件。通过 FormData.append() 方法赋给字段 "accountnum" 的数字被自动转换为字符(字段的值可以是一个 Blob 对象,File对象或者字符串,剩下其他类型的值都会被自动转换成字符串)。
在该例子中,我们创建了一个名为 oMyForm 的 FormData 对象,该对象中包含了名为"username","accountnum","userfile" 以及 "webmasterfile" 的字段名,然后使用XMLHttpRequest的 send() 方法把这些数据发送了出去。"webmasterfile" 字段的值不是一个字符串,还是一个 Blob 对象。
使用HTML表单来初始化一个FormData对象
可以用一个已有的 form 元素来初始化 FormData 对象,只需要把这个 form 元素作为参数传入 FormData 构造函数即可:
var newFormData = new FormData(someFormElement);
例如:
var formElement = document.getElementById("myFormElement");
var oReq = new XMLHttpRequest();
oReq.open("POST", "submitform.php");
oReq.send(new FormData(formElement));
你还可以在已有表单数据的基础上,继续添加新的键值对,如下:
var formElement = document.getElementById("myFormElement");
formData = new FormData(formElement);
formData.append("serialnumber", serialNumber++);
oReq.send(formData);
你可以通过这种方式添加一些不想让用户编辑的固定字段,然后再发送. 使用FormData对象发送文件
你还可以使用 FormData 来发送二进制文件.首先在 HTML 中要有一个包含了文件输入框的 form 元素:
<form enctype="multipart/form-data" method="post" name="fileinfo"> <label>Your email address:</label> <input type="email" autocomplete="on" autofocus name="userid" placeholder="email" required size="32" maxlength="64" /><br /> <label>Custom file label:</label> <input type="text" name="filelabel" size="12" maxlength="32" /><br /> <label>File to stash:</label> <input type="file" name="file" required /> </form> <div id="output"></div> <a href="javascript:sendForm()">Stash the file!</a> |
然后你就可以使用下面的代码来异步的上传用户所选择的文件:
function sendForm() { var oOutput = document.getElementById("output"); var oData = new FormData(document.forms.namedItem("fileinfo")); oData.append("CustomField", "This is some extra data"); var oReq = new XMLHttpRequest(); oReq.open("POST", "stash.php", true); oReq.onload = function(oEvent) { if (oReq.status == 200) { oOutput.innerHTML = "Uploaded!"; } else { oOutput.innerHTML = "Error " + oReq.status + " occurred uploading your file.<br \/>"; } }; oReq.send(oData); } |
你还可以不借助 HTML 表单,直接向 FormData 对象中添加一个 File 对象或者一个 Blob 对象:
data.append("myfile", myBlob);
如果 FormData 对象中的某个字段值是一个 Blob 对象,则在发送 HTTP 请求时,代表该 Blob 对象所包含文件的文件名的 "Content-Disposition" 请求头的值在不同的浏览器下有所不同,Firefox使用了固定的字符串"blob",而 Chrome 使用了一个随机字符串。
你还可以使用 jQuery 来发送 FormData,但必须要正确的设置相关选项:
var fd = new FormData(document.getElementById("fileinfo")); fd.append("CustomField", "This is some extra data"); $.ajax({ url: "stash.php", type: "POST", data: fd, processData: false, // 告诉jQuery不要去处理发送的数据 contentType: false // 告诉jQuery不要去设置Content-Type请求头 }); |
浏览器兼容性
在做
单元测试时,
代码覆盖率常常被拿来作为衡量测试好坏的指标,甚至,用代码覆盖率来考核测试任务完成情况,比如,代码覆盖率必须达到80%或 90%。于是乎,测试人员费尽心思设计案例覆盖代码。用代码覆盖率来衡量,有利也有有弊。本文我们就代码覆盖率展开讨论,也欢迎同学们踊跃评论。
首先,让我们先来了解一下所谓的“代码覆盖率”。我找来了所谓的定义:
代码覆盖率 = 代码的覆盖程度,一种度量方式。
上面简短精悍的文字非常准确的描述了代码覆盖率的含义。而代码覆盖程度的度量方式是有很多种的,这里介绍一下最常用的几种:
1. 语句覆盖(StatementCoverage)
又称行覆盖(LineCoverage),段覆盖(SegmentCoverage),基本块覆盖(BasicBlockCoverage),这是最常用也是最常见的一种覆盖方式,就是度量被测代码中每个可执行语句是否被执行到了。这里说的是“可执行语句”,因此就不会包括像C++的头文件声明,代码注释,空行,等等。非常好理解,只统计能够执行的代码被执行了多少行。需要注意的是,单独一行的花括号{} 也常常被统计进去。语句覆盖常常被人指责为“最弱的覆盖”,它只管覆盖代码中的执行语句,却不考虑各种分支的组合等等。假如你的上司只要求你达到语句覆盖,那么你可以省下很多功夫,但是,换来的确实测试效果的不明显,很难更多地发现代码中的问题。
这里举一个不能再简单的例子,我们看下面的被测试代码:
int foo(int a, int b)
{
return a / b;
}
假如我们的测试人员编写如下测试案例:
TeseCase: a = 10, b = 5
测试人员的测试结果会告诉你,他的代码覆盖率达到了100%,并且所有测试案例都通过了。然而遗憾的是,我们的语句覆盖率达到了所谓的100%,但是却没有发现最简单的
Bug,比如,当我让b=0时,会抛出一个除零异常。
正因如此,假如上面只要求测试人员语句覆盖率达到多少的话,测试人员只要钻钻空子,专门针对如何覆盖代码行编写测试案例,就很容易达到主管的要求。当然了,这同时说明了几个问题:
1.主管只使用语句覆盖率来考核测试人员本身就有问题。
2.测试人员的目的是为了测好代码,钻如此的空子是缺乏职业道德的。
3.是否应该采用更好的考核方式来考核测试人员的
工作?
为了寻求更好的考核标准,我们必须先了解完代码覆盖率到底还有哪些,如果你的主管只知道语句覆盖,行覆盖,那么你应该主动向他介绍还有更多的覆盖方式。比如:
2. 判定覆盖(DecisionCoverage)
又称分支覆盖(BranchCoverage),所有边界覆盖(All-EdgesCoverage),基本路径覆盖(BasicPathCoverage),判定路径覆盖(Decision-Decision-Path)。它度量程序中每一个判定的分支是否都被测试到了。这句话是需要进一步理解的,应该非常容易和下面说到的条件覆盖混淆。因此我们直接介绍第三种覆盖方式,然后和判定覆盖一起来对比,就明白两者是怎么回事了。
3. 条件覆盖(ConditionCoverage)
它度量判定中的每个子表达式结果true和false是否被测试到了。为了说明判定覆盖和条件覆盖的区别,我们来举一个例子,假如我们的被测代码如下:
int foo(int a, int b)
{
if (a < 10 || b < 10) // 判定
{
return 0; // 分支一
}
else
{
return 1; // 分支二
}
}
设计判定覆盖案例时,我们只需要考虑判定结果为true和false两种情况,因此,我们设计如下的案例就能达到判定覆盖率100%:
TestCaes1: a = 5, b = 任意数字 覆盖了分支一
TestCaes2: a = 15, b = 15 覆盖了分支二
设计条件覆盖案例时,我们需要考虑判定中的每个条件表达式结果,为了覆盖率达到100%,我们设计了如下的案例:
TestCase1: a = 5, b = 5 true, true
TestCase4: a = 15, b = 15 false, false
通过上面的例子,我们应该很清楚了判定覆盖和条件覆盖的区别。需要特别注意的是:条件覆盖不是将判定中的每个条件表达式的结果进行排列组合,而是只要每个条件表达式的结果true和false测试到了就OK了。因此,我们可以这样推论:完全的条件覆盖并不能保证完全的判定覆盖。比如上面的例子,假如我设计的案例为:
TestCase1: a = 5, b = 15 true, false 分支一
TestCase1: a = 15, b = 5 false, true 分支一
我们看到,虽然我们完整的做到了条件覆盖,但是我们却没有做到完整的判定覆盖,我们只覆盖了分支一。上面的例子也可以看出,这两种覆盖方式看起来似乎都不咋滴。我们接下来看看第四种覆盖方式。
4. 路径覆盖(PathCoverage)
又称断言覆盖(PredicateCoverage)。它度量了是否函数的每一个分支都被执行了。 这句话也非常好理解,就是所有可能的分支都执行一遍,有多个分支嵌套时,需要对多个分支进行排列组合,可想而知,测试路径随着分支的数量指数级别增加。比如下面的测试代码中有两个判定分支:
int foo(int a, int b)
{
int nReturn = 0;
if (a < 10)
{// 分支一
nReturn += 1;
}
if (b < 10)
{// 分支二
nReturn += 10;
}
return nReturn;
}
对上面的代码,我们分别针对我们前三种覆盖方式来设计测试案例:
a. 语句覆盖
TestCase a = 5, b = 5 nReturn = 11
语句覆盖率100%
b. 判定覆盖
TestCase1 a = 5, b = 5 nReturn = 11
TestCase2 a = 15, b = 15 nReturn = 0
判定覆盖率100%
c. 条件覆盖
TestCase1 a = 5, b = 15 nReturn = 1
TestCase2 a = 15, b = 5 nReturn = 10
条件覆盖率100%
我们看到,上面三种覆盖率结果看起来都很酷!都达到了100%!主管可能会非常的开心,但是,让我们再去仔细的看看,上面被测代码中,nReturn的结果一共有四种可能的返回值:0,1,10,11,而我们上面的针对每种覆盖率设计的测试案例只覆盖了部分返回值,因此,可以说使用上面任一覆盖方式,虽然覆盖率达到了100%,但是并没有测试完全。接下来我们来看看针对路径覆盖设计出来的测试案例:
TestCase1 a = 5, b = 5 nReturn = 0
TestCase2 a = 15, b = 5 nReturn = 1
TestCase3 a = 5, b = 15 nReturn = 10
TestCase4 a = 15, b = 15 nReturn = 11
路径覆盖率100%
太棒了!路径覆盖将所有可能的返回值都测试到了。这也正是它被很多人认为是“最强的覆盖”的原因了。
还有一些其他的覆盖方式,如:循环覆盖(LoopCoverage),它度量是否对循环体执行了零次,一次和多余一次循环。剩下一些其他覆盖方式就不介绍了。
总结
通过上面的学习,我们再回头想想,覆盖率数据到底有多大意义。我总结了如下几个观点,欢迎大家讨论:
a. 覆盖率数据只能代表你测试过哪些代码,不能代表你是否测试好这些代码。(比如上面第一个除零Bug)
b. 不要过于相信覆盖率数据。
c. 不要只拿语句覆盖率(行覆盖率)来考核你的测试人员。
d. 路径覆盖率 > 判定覆盖 > 语句覆盖
e. 测试人员不能盲目追求代码覆盖率,而应该想办法设计更多更好的案例,哪怕多设计出来的案例对覆盖率一点影响也没有。
使用HP
UFT 12.00版本(主要是
GUI测试)一段时间,有些小想法,本来是打算回复给hp相关人员,奈何邮件一直遭拒,所以暂且先放在这里吧。
关于HP 的UFT,简单做一下评价,总体来说是一款很好的软件,极容易上手和使用(这个也估计只是我没有往深入研究),
测试流程和思路也很清晰,重点是和HP一些其他自动化工具,比如Loadrunner,QC可以结合起来用,堪称完美。拍完马屁说点实际的。
其实对于目前所在公司的项目,我一直很犹豫要不要引进HP的这款软件,首先我对于这款软件不是很熟悉,当然现在这个理由是可以排除的,对于这个软件的效果怎么样,我也不确定,之前公司没有使用这个软件,所以也没有什么积累,一切都要从零开始。明确知道这个软件可能在后期的回归测试会减少点人力,但是前期的的脚本录制,调试,以及维护,感觉也不是一个小工程。
学习使用HP UFT软件的过程不算很艰难,网上的资料还算是比较多的,加上采用录制的模式,只需点点点的就可以了,使用起来还是很轻松的,当然也会遇到一些问题,不过在网上查查资料基本上可以解决大部分。底层的脚本录制基本上解决的差不多了,开始关注整个测试流程框架,也就是在这个地方,突然间发现HPUFT的GUI测试不灵活,感觉有些死板。
简单说就是动作Action的执行顺序只能按你设定好的跑,由于之前本人是测试android产品的,有使用过Monkeyrunner相关的测试工具,对于这种按设定路线走的
自动化测试工具有些不是很习惯。于是乎,突发奇思妙想,要是HPUFT的GUI测试也可以做到这种随机的测试就好了,当然了也不是像Monkeyrunner那样。
初步的想法是将各个Action进行封装,采用随机调用的方式执行测试,调用的次数可以是随机的也可以设定,这个整体架构的改变会对Action的录制有相应的要求,比如说执行完一个Action,它的出口和入口要一致,各个Action的要在同一级别,比如在同一页面。如此可能会出现多个层次,比如一个Action中又可以划分出多个Action,这个需要采用分层的思想进行解决,至于要分多少层,使用者可以按照自己的软件的特点进行划分。
说的好像又些复杂了,简单的说就是按照一定的要求录制Action,采用随机的方式执行Action。此方案可以检测出不同功能之间因调用顺序的不同而出现的BUG,实际测试也证明这种BUG是存在的,同时,这种模式也使得该软件的使用更为灵活。
暂且想法就这些,后续有再补上。