一.测试文档的组成
1.测试计划
2.测试用例
3.缺陷报告
4.测试工具和自动测试
5.度量,统计和总结
二.软件产品由哪些部分组成
1.最终产品
2.帮助文件
3.用户手册
4.样本和示例
5.标签和帖子
6.产品支持信息
7.图标和标志
8.错误信息
9.广告和宣传材料
10.安装
11.说明文件
三.软件生命周期
1.需求分析
2.设计(概要设计、详细设计)
3.编码
4.测试
5.维护
四.软件开发的模式
1.大爆炸模式
计划、进度、安排和正规开发过程几乎没有
所有的经历都花在编写代码上
适用于产品无需很好理解而且最终发布日期可以随便更改
多数情况下,大爆炸模式几乎没有什么测试
2.边写边改模式
至少考虑了产品需求
通常最初只有粗略的想法,接着进行一些简单的设计,然后开始漫长的来回编写、测试和修改缺陷的过程
适用于快速制作而且用完就扔的小项目
测试在该模式中未特别强调
3.瀑布模式
强调产品的定义
各步骤是分立的,没有交叉
瀑布模型无法回溯
适用于明确清晰的产品定义和训练有素的开发人员的项目
测试在开发完成后进行,无法尽早发现需求和设计中的缺陷
4.螺旋模式
从小开始
定义重要功能
努力实现这些功能
接受用户反馈
进入下一阶段
上次说到已经配好了MonkeyRunner的运行环境,现在讲解怎么进行简单的MonkeyRunner测试。这个拖了很久才有时间和心情总结一下。真是计划赶不上变化啊。
就不说废话了。http://developer.android.com/tools/help/MonkeyDevice.html ,这个地址是android上的MonkeyRunner官方文档地址。上面关于MonkeyRunner的一些方法说的很详细。本篇不是详细解释MonkeyRunner方法的文章,只是根据我的理解,结合自己的一个例子讲解一下怎么使用MonkeyRunner写测试程序。
MonkeyRunner工具共有三个类:MonkeyRunner、MonkeyDevice、MonkeyImage。
一、MonkeyRunnner类:
这个类主要用于把MonkeyRunner连接至真机或者模拟器,另外有个显示提示信息的alert方法和一个选择方法比较常用。
二、MonkeyImage类:
这个类主要是测试过程中,用来保存测试截图,把图片保存成各种格式,并且可以比较两个MonkeyImage对象。
主要常用的有两个方法:writeToFile (string path, string format) ,这个是保存截图的位置,和命名,可以把图片保存到sdcard里面,也可以保存到电脑里面;sameAs (MonkeyImage other, float percent) ,这个用来比较两个截图是否相同,结果为boolean值。
三、MonkeyDevice类:
这个类的方法就多了一下,主要提供了安装和卸载程序包、启动一个活动及操作键盘等事件的方法。像里面的startActivity (string uri, string action, string data, string mimetype, iterable categories dictionary extras, component component, flags) 启动一个应用,press (string name, dictionary type)点击控件 ,touch (integer x, integer y, integer type)点击控件(通过坐标定位) ,drag (tuple start, tuple end, float duration, integer steps) 拖拉控件等等方法比较常用。
MonekeyRunner这个测试工具用起来不算太难,下面给出一段代码简单解释一下:
FetionTest.py
#coding=utf-8 Python程序的第一句,添加这句使注释支持中文
# 导入需要的MonkeyRunner的包,也可以是自己写的类,如果文件很多的话
from com.android.monkeyrunner import MonkeyRunner,MonkeyDevice,MonkeyImage
#连接真机,里面要写正确自己的真机对应的名字,cmd下命令下,可以用adb devices 看到
devices1=MonkeyRunner.waitForConnection(5,"1234567890ABCDEF")
#连接模拟器
devices2=MonkeyRunner.waitForConnection(5,"emulator-5554")
#启动飞信的主界面,这是我测的飞信程序,里面主要是配好飞信的包名和启动Activity
devices1.startActivity
(component = 'cn.com.fetion/.android.ui.activities.StartActivity')
devices2.startActivity
(component = 'cn.com.fetion/.android.ui.activities.StartActivity')
#休眠8秒,便于帐号的登录
MonkeyRunner.sleep(8.0)
#点击我的好友一栏,这里使用的是坐标方法
devices1.touch(202,258,"DOWN_AND_UP")
#选中好友
devices1.touch(222,326,"DOWN_AND_UP")
devices1.touch(442,325,"DOWN_AND_UP")
# 点击“加入黑名单”
devices1.touch(223,563,"DOWN_AND_UP")
# 下面是进行截图,和保存图片
result1 = device1.takeSnapshot()
result1.writeToFile('E:\slack01.png','png')
#点击返回键,可以找到模拟器上任何一个建对应的关键值,这个在官方文档里可以查到
device1.press('KEYCODE_BACK',MonkeyDevice.DOWN_AND_UP)
result2 = device1.takeSnapshot()
result2.writeToFile('E:\slack02.png','png')
这个程序就简单说这些,另外可以对自己的程序进行优化,可以把不同的测试case封装到一个方法里,也可以把一些配置设置成全局变量,然后在不同的类里进行调用。这些需要简单了解一下Python的编程知识。
下面说一个比较好用的获取坐标的工具。就是用来获取真机上坐标的工具,当我们点击真机上的空间时,能显示真机上的点击点的坐标。
这个是一个可以自己定义的python文件,只有几句话:
from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner.recorder import MonkeyRecorder as recorder
# 里面添加你的真机的名字
device = mr.waitForConnection(5,"MSM8225QRD5")
recorder.start(device)
比如,我把它定义为monkey_recorder.py, 然后放在G盘,在cmd下运行 monkeyrunner monkey_recorder.py ,就会出现如下界面:
当我们在这个工具上点击时,真机也会跟着执行,并且会显示每一步我们点击的位置的坐标,比如,我点击打开音乐播放器会记录如下:
这个工具的好只有处你用过几次才知道的,呵呵,反正有些应用程序里很小的图标只能通过坐标获取点击事件,这个工具的优势就显示出来了。另外,需要注意,这个是不能用来获取模拟器坐标的,模拟器上的坐标只有自己下个截图工具,来量取坐标了。这一点确实很蛋疼。
软件工具都是这样,没有绝对的便利。MonkeyRunner也不例外。呵呵,这一次就写这么多吧,这个工具简单的使用介绍的也差不多了,更多的还需要自己去摸索,熟能生巧嘛。希望大家能多多分享,多多交流。
相关链接:
6.15.8 如何对结果数据进行有效的分析
为了将原始数据和分析数据进行比较,这里我们新建一个sheet页,名称叫做“数据分析”,并将这些数据复制到新的sheet页,如图6-206所示,而后将最小值、最大值和平均值记录在该sheet页。
图6-206 “思考时间测试事务”数据分析
以上是我们用Excel计算出来的,接下来,将这些数据和结果概要信息进行比对,即:比对图6-206和图6-207关于“思考时间测试事务”的最小值、平均值和最大值,您也许已经发现了最小值和最大值是完全一致的,而平均时间是有差异的,由于Excel文件中的平均值没有进行四舍五入,所以比结果概要信息中的数据是多的,经过四舍五入后它们就完全一致了。
图6-207 “Analysis Summary”信息
也许有的读者还非常关心“Std.Deviation”和“90 Percent”是怎么来的呢?首先,我先解释一下“Std.Deviation”和“90 Percent”代表什么意思:“Std.Deviation”是标准偏差,它代表着事务数据间差异大小程度,这个数值越小越好。
这里我们用标准偏差S来表示、平均值用表示、每个具体的数据值用X1表示,用N来表示数据的个数。
接下来,我们把具体的数值放入公式中,为了方便我们计算,这里分步来计算。
第一步:
[(1.013-6.511)2+(2.016-6.511)2+(3.013-6.511)2+(4.016-6.511)2+(6.016-6.511)2+ (6.014-6.511)2+(6.999-6.511)2+(7.999-6.511)2+(9.016-6.511)2+(10.016-6.511)2]/10=8.2399385,这里我们保留小数点后4为,则为8.2399。
第二步:将8.2399开平方后,得到S为2.8705,保留小数点3位,S=2.871。
接下来我们同图6-207中的“思考时间测试事务”的标准偏差进行对比,发现两者是一致的,当然我们手工进行计算似乎有点过于繁琐,有没有什么简易的方法就可以计算出标准偏差呢?这里我非常兴奋地告诉您:“有”!Excel提供了非常丰富的函数,我们可以利用“STDEVP”函数,如图6-208所示。该函数给出的整个样本总体的标准偏差,标准偏差反映相对于平均值的离散程度,该值越小越好。
图6-208 Excel标准偏差函数的应用
接下来,让我们看一下“90 Percent”是怎么得来的,它是指90%“思考时间测试事务”中最大的值,这里因为一共有10条记录,排序后则9.015是这90%里边最大值,所以“90 Percent”即为该值,如图6-209所示。
图6-209 “思考时间测试事务”的数据信息
当然,您也可以通过添加“Transaction Response Time (Percentile)”图表来查看,如图6-210所示。
图6-210 添加图表信息对话框信息
因为这里我们的事务需要包含思考时间,所以,需要单击图6-210中的“Filter & Open”按钮,将弹出图6-211所示对话框,您需要选择“Include Think Time”,即包含思考时间,然后再单击“OK”按钮,则会出现图6-212所示界面,您如果想了解90%“思考时间测试事务”,可以顺着横坐标90位置往上找和图表曲线的交点,该点即为“90 Percent”的值。
图6-211 “Graph Settings”对话框
图6-212 “Transaction Response Time (Percentile)”信息
(未完待续)
版权声明:51Testing软件测试网及相关内容提供者拥有51testing.com内容的全部版权,未经明确的书面许可,任何人或单位不得对本网站内容复制、转载或进行镜像。51testing软件测试网欢迎与业内同行进行有益的合作和交流,如果有任何有关内容方面的合作事宜,请联系我们。
相关链接:
精通软件性能测试与LoadRunner最佳实战 连载一
精通软件性能测试与LoadRunner最佳实战 连载二
自动化测试能否帮助我们我们提升开发效率,关键在于其有效性。如果其有效性可能存在问题,那么可能是什么导致了这种问题的产生呢?对自动化测试产生作用的方式存在误解,对自动化测试能够产生作用所要求的条件存在误解,自动化测试分析设计的随意性,自动化测试开发维护的低标准,对自动化测试资产的低准出条件……本文将就自动化测试有效性简单阐述我自己的一点见解,抛砖引玉。
观念之一:独木难生于漠,密植方育甘霖
沙漠中间栽下一棵树,枯死只是迟早之事;即便有足够的资源让它能够永久地生存下去,而它除了给路过的摄影师的构图上增添一分绿色气息,便再也没有其它存在的意义了。如果要想它能够长久而有生命力地活下去,并期望它能够改善生态,那就需要将其根植在一片密林之中。自动化测试,尤其是前端自动化测试,如若离开其他层次的自动化测试和技术手段与之相互配合,便会如同这棵沙漠中间的树一样,不久便灭。
五一节后的周五下午和两个开发经理一起review一个项目的性能测试需求,休息闲聊时顺道提到了其中一位开发经理所负责的公网系统CI连续飘红的问题。他坦言自己对自动化测试是不信任的,认为自动化测试发现不了任何问题,从而对CI带来的帮助表示怀疑,因此不愿意在CI上投入过多的精力。我理解这位经理的感慨,认同他对自动化测试有效性的担忧,但是这并不能使我认同他对CI和自动化测试的态度。而对于软件开发来说,没有自动化测试、没有CI,我们可能无法期待更高效率的开发和更好的质量保证。但他这是因为没有耐心地植下一片树林,才导致他心目中自动化测试这棵大树的死亡,进而却又否认这片树林存在的意义,是不妥的。
观念之二:降低期望于心,提升目标于行
为什么有很多人对自动化测试的可信任程度表示怀疑呢?这源于一句看似真理的废话:自动化不是用来发现缺陷的,而是为了验证系统改动是否造成关联影响并用以增加质量信心的。这话本意无非就是想告诉大家:不要对自动化测试的结果抱太高期望,对机器智能和人脑的差距要认清。这个本意本无可厚非,但是这句话现在却逐渐成了自动化测试低质量的借口了。
首先,很多人认为自动化测试无法发现缺陷是因为测试脚本无法代替人类的自主思考,无法灵活变通。而我们之所以期望在测试执行时能够灵活变通,是因为测试分析设计时对被测应用设计细节的不确定性。非探索式的脚本化测试设计就是要通过输入精确的操作和数据类型,从而得到精确的输出来和预期结果进行对比,在这种模式下,没什么问题是需要变通才能被发现的。
其次,不得不说自动化测试分析设计的难点在于对输入条件分支和状态机组合的穷举和选择上,这需要很大的成本。不幸的是,通常在做自动化测试分析设计时,我们习惯于用经验和主观感觉去做,不全面和主次不分的情况常常出现。因而,自动化测试分析和设计的不严谨、不完整也是自动化测试不能被信任的原因之一。
此外,自动化在那些已覆盖的测试上面表现的也不尽如人意,笔者观察到,大多数人并不是用自动化测试脚本去做测试或质量守护工作,而只是用来让他们运行通过,以获得KPI的达成和成就感。自动化测试运行失败,不习惯于或不乐于使用它的同事就会很轻易忽略潜在的问题,所以漏测和封版延期是常有的事。
最后打个比方,自动化测试就像是看门的狗,没有它,家里进贼未必发现不了,关键是发现的时候是否还来得及挽回损失,而狗狗能不能在有贼光顾的时候示警,取决于我们如何驯养它。我们期望家里进贼的时候狗狗能够示警,但并不会期望它能够帮我们直接把小贼擒住;而且并非每次狗狗示警都意味着有贼入室,也有可能是阁下经过而已。自动化测试与此类似,凡其所致,但有一点不一致便会得到失败的结果,而至于是不是发现了虫子却又是另外一码事,想用自动化帮你解决所有问题,那你还是洗洗睡吧。
总之,我们对自动化测试有什么样的期望,就应该有什么样的目标和要求;设定了什么层次的测试目标,就应该有什么样的使用效果。如果告诉你自动化测试可以朝着任意品种的狗狗去培养,目标就是看家护院,你非要以哈士奇食量大喂不起为由非要选小泰迪,那能有什么办法呢?如果自动化测试分析设计不严谨,而已覆盖的脚本也没有严格编写,而或脚本严格编写却并未善加利用,那么自动化测试自然就是一个彻底的谎言了。
我们可以期望用自动化测试来暴露所有的问题供使用者抉择,而非用来精确定位每一个问题。即便我们不期望自动化测试发现所有的BUG,但是我们必须要用发现每一个BUG的标准去分析设计和利用我们的自动化测试用例。
方法之一:善用手动测试用例库
如果有完整或近乎完整的手动测试用例库,在做自动化测试的时候用它来甄别自动化测试范围、做自动化测试需求分析和设计是非常棒的,可达事半功倍之效。因为除了SLA签订的关键功能清单,我们可以分析测试用例库中最近X个月、Y个版本中被执行的测试用例的频率和比例等等数字,从而很容易得出最有价值做自动化测试的范围,而如有其他因素也可以加入综合评估。这时候,关联或映射手动测试用例和自动化测试脚本可以很容易地衡量自动化测试的覆盖率等相关指标。
除此之外,手动测试用例之于自动化测试开发的意义还在于一种特定模式下的自动化测试开发:测试人员按照一种特定的规约编写产品或项目的手动测试用例,随后用一种模型化的转换规则去生成自动化测试脚本,继而再去修改和优化。这种模式的好处是,自动化测试脚本的开发者不需要懂业务知识,只需要按照业务测试人员编写的详尽的测试用例去实施。而不足之处却也十分明显,由于思维方式上的差异,编写测试脚本过程中的沟通成本不会太低,而且对建模水平要求很高。
方法之二:搭建适应性强的框架
开源测试工具,我们组先后用过Selenium RC、WebDriver、WatiJ这几种工具模式,框架采用JUnit和TestNG,测试调度和CI使用Jenkins管理。做起来最大的感受就是,像WebDriver这种工具,它本质上是为互联网而生;如果要用于我们的企业ERP应用的自动化测试,如果没有做一系列适应性的API和组件封装,测试脚本开发对于大部分同事来说都是件头疼的事情。
经过摸索,我们结合PAFA(PingAn Foundation Architecture)架构的前端页面特征做了对象识别、页面加载和处理缓冲、运行监听、数据处理、断言和运行容错、报告、日志以及第三方组件(如Autoit、Cmd等)兼容的封装操作。这种封装属于Bot-Style,适用于我们基于业务操作驱动的自动化测试用例编写,能够大大地降低我们测试脚本开发的技术成本。虽然它们看起来运行起来效率并不是十分高,但是对于被测应用的响应速度来说已经是数十倍的效率胜出了。至于使用Page Object还是Bot-Style,还请大师们不要吐我的槽,这没什么可争论的,因为这只是面向前端业务操作的页面测试,二者的表现毫无优劣之分,完全在于队员的测试脚本编写风格偏好。
附图1:测试中的TestCase样例
附图2:测试中的TestApi样例
我们正在从瀑布开发模式下逐渐开展持续集成和敏捷转型,不难预见,达到一定成熟度之后我们大部分团队会使用ATDD或BDD模式。这种快速开发的基于前端的自动化验证测试虽然在验证测试中所占的比例会越来越小,但它们将依然有用武之地,同时对当下的系统前端的回归测试也可以完全支持。
自动化测试框架强大与否主要在于其适应性,所以与其说自动化测试框架是选择出来的,不如说它是被改造出来的。产品比较单一的小公司或者开发框架比较成熟的公司用一个工具、一个框架在一个测试平台下就可以解决一个公司的自动化测试实施的绝大多数问题,而大型企业的应用繁多复杂,很难由一个简单工具或者框架来解决。
自动化测试框架,没有自己DIY过的永远都不要相信不造轮子这种说法,因为只有在你做的时候,你才能够更深入地理解你的工具、框架和你的被测应用及其所依赖的开发框架的特征,才会产出更具适应性的测试框架和特征库。
方法之三:善用设计与重构
无论是前端自动化测试还是单元测试,脚本代码结构的重要性是个老生长谈,因为这关乎测试脚本代码的可读性、维护经济性等问题。由于我们的业务系统的测试案例主要是基于操作流程的业务场景,所以我简单说一下我们在这方面的做法与经验,而单个功能点特性的测试分析也基本与此类同,不再独占篇幅。
测试范围与设计
大型企业,尤其是金融行业的ERP应用,很多系统实际上都是规则零散、流程复杂,规则引擎应用得并不是很充分。这便是前文所提及的自动化测试分析设计时对输入条件分支和状态机组合的穷举和选择困难的问题的产生原因了。对于这种情况,我们可以借鉴两个基础的测试设计方法:正交覆盖和Pairwise/All-Pairs,其基本原理和用法这里不再赘述。
以正交覆盖为例,我们需要把影响流程分支走向的因子罗列出来。一般理论认为,缺陷的产生绝大多数是因为2个因素组合产生的,所以我们先做一个正交强度为2的配对来确认最小的测试组合范围。然后将正交强度变为最大,得到一个最大的测试组合范围,重新审视强度为2时的子集之外的其他案例是否有必测的理由。注:正交强度最大的组合结果集并不是简单的笛卡尔乘积,因为组合条件之间可能存在一些逻辑限制;此外,鉴于篇幅有限,本文就不再罗列下表最终正交得出的测试组合结果。
图3:保险业务操作伪场景分析正交表样例
通过对前端业务操作流程的分析,可以建立完整的前端的测试用例库,当然,前端的自动化测试可能只会选择其中的一部分去做,如上文所述,自动化测试覆盖率也可以很轻易地度量出来。在设计评审过程中,流程设计完成者需要向评审人员展示各个业务流程的测试组合结果,并且解释通过怎样的组合方法得到这些结果;进一步阐述依据什么样的原则得出自动化测试范围的选择结果。
特性组件抽取
完成了对测试案例场景的界定,我们便已明确我们要在被测应用上做什么样的操作,接下来的工作就是一些通用的简易特性抽取。虽然很多人声称复用不是个好东西,但它在编写测试脚本中适度的使用,却也可以很好地减少测试脚本开发和维护成本。
第一层的特性抽取可以通过测试框架和工具来完成,简易的二次封装是保证测试书写低复杂度和保证测试脚本健壮性的好办法,就如同图中对click方法的简单封装一样。这件事情只需要一个像笔者一样略微熟悉测试工具和被测应用特性的测试工程师即可完成。所以这个活动的成本不会太高,而带来的效益却还是很不错的,至少可以给不熟悉测试工具的脚本开发者一段很长的学习缓冲期。
图4:STAR测试框架中的Api样例
接下来就是被测对象的共有特性组件做抽取,仍以前端自动化测试脚本开发为例,我们可以参照被测应用本身的组件复用规律来提取测试脚本中的公用页面组件。例如,图3所涉及的保全操作流程中,至少包含保全申请发起、质检、核保这三个可公用的页面组件。假如最终需要测试的场景案例有8个,其中5个必须质检、4个必须人工核保,那么在这三个点上就可以节省14(8+5+4-3)个测试Api的维护和开发成本。
共有特性组件既不能设计的过于粗糙,从而造成在不同的自动化测试脚本中无法统一支持;又不能过度抽象,进而导致可读和可维护性变差。目标很简单:任意一处纯前端页面的改动,我们只需要修改一个测试Api;任意一个分支场景的规则改动,我们只需要修改一个测试案例脚本的数据和验证方法。
脚本重构与舍弃
虽然测试脚本应该被良好地规划设计,但实际执行的时候可能会遇到一些问题,比如在识别公用特性组建时存在遗漏却未在评审环节被发现,抑或被测应用的代码重构甚至是流程再造同样会要求我们去做这些对应的测试脚本重构的工作。
我每天都会从SVN上checkout我们组17个工程的测试脚本,之前经常会发现某些系统一次性update十数个甚至数十个文件。这时候我会问一声为什么,最初得到的答复都是由于系统页面发生变动,所以这部分测试脚本都需要同步修改。我的回复一律都是:重构,重新做公用组件抽取,立刻做,因为我们耗不起这个维护成本!几次之后,再遇到这种情况,得到的答复便基本上都是:因为XX的变更影响了一些模块的测试脚本,所以我重构了一下。这时候我的心里就比较嗨皮,比较有成就感了,因此我也乐于不断地给大家灌输这种从别处学来的理念,而测试脚本重构的动力也从单纯的前端页面变更驱动变得更加多样化。
伴随着测试脚本的开发,债务不可避免的在不断产生,如果不加以管控,终有一天我们都会由于对之无法负担而崩溃。所以我们如果真的重视自动化测试,我们就需要不断地优化我们测试脚本的结构,让它始终停留在我们可以控制的范围之内。必要的时候我们可以放弃一些已经开发完成的但是优先级稍低的测试脚本,即便它们运行得很好。在负担能力满载的情况下,以放弃低优先级的部分为代价来换回我们对测试资产持续优化和维护的能力、换回我们对这种低端技术债务的负担能力,始终都比承担不可控甚至崩溃的风险要来得更加有价值。
方法之四:坚持定期脚本审查
在团队里面推广自动化测试应用,同样也会存在很多沟通和理解上的误差,加之不是所有人的技能都在一个层次上,所以大家可能对测试脚本开发的各种要求的理解上都会存在不同的误区。因此,测试脚本开发完成之后第一时间要进行审查,这是测试脚本有效性验证的第一道关,虽然这个活动将耗费不少时间。
测试脚本审查的手段分两种:工具检查和评审会议。一般建议在召开评审会议之前使用代码扫描工具如sonar或者checkstyle、 findbugs等,定义一些通用的规则去检查测试脚本书写和设计结构上的问题。使用工具的方法本文不介绍,完成这些基础不合规项的检查和修复之后则可以开始后续的测试有效性评审。测试有效性评审是个技术活也是个体力活,不仅需要很强大的业务知识支撑和对系统设计模式甚至架构的理解,同时也需要足够的时间和耐心来从事这项活动。接下来,简单说一些实际的检查点,通过这些检查点,我们将识别一段测试脚本是否能够达成测试目标,是否经济合理。
● 经过设计评审的每一个案例是否都已经完成开发,是否存在额外的新增覆盖,为什么;
● 预设的公用组件是否已经完全抽离实现,对于假设的被测应用修改,能否用最简单、最经济的测试脚本修改来支持同步;
● 测试数据的初始化和使用是否合理,是否会破坏测试环境中数据的健康度甚至带来环境故障;
● 测试的验证点是否精准足够,是否存在反向的验证方式(如,只检查是否有错误发生);
● 测试脚本中是否存在多余的结构体,是否存在非预设的随机性分支;
● ……
子曰:脚本三月不复查,CI即便全蓝——虫子照样遍地爬!自动化的测试有效性本就是需要通过维护去保持的,定期重新审查本就是一种测试脚本的维护手段,是非常有必要的。而频率则需要结合团队对测试脚本维护的力度来看,复审的主要的方法与开发完成之后的初次审查是一样的,只是关注点会稍有不同。
两句废话做总结
自动化测试是达成目标的工具手段还是负担,关键在于它对我们产生了什么样的影响,要产生好的效用,就必须保证其测试有效性。保证自动化测试有效性的手段很多,笔者目前的水平只能说到这里,但愿对入门者和彷徨者有所助益,高手和喷子们便一笑而过便可。
MySQL建表阶段是非常重要的一个环节,表结构的好坏、优劣直接影响着后续的管理维护,赶在明天上班前分享总结个人MySQL建表、MySQL查询优化积累的一些实用小技巧。
技巧一、数据表冗余记录添加时间与更新时间
我们用到的很多数据表大多情况下都会有表记录的“添加时间(add_time)”,我建议大家再新增一个记录“更新时间(update_time)”字段,在我的工作里需要为市场部、运营部等建立各种报表,而很多报表里的数据都是需要到大记录表里去查询的,如果直接查询大表的话,查询速度相当慢。那么,我们的解决办法就是建立各种数据快照,数据快照都是通过脚本定时跑的。假如,哪一天原始数据表出现了问题,那么快照数据也会有问题,这个时候我们的快照脚本就得重新跑一份修复好了的数据,这样有了update_time更新时间这个字段,我们就能很快定位记录更新的时间。
技巧二、不要抛弃insert ignore into与replace into
大家肯定写过一些脚本循环将记录插入到数据表,例如使用PHP脚本。如果期间的一条SQL插入失败,那么后续的插入将终止,你可以在脚本里绕过这个插入错误,一个好的办法是使用ignore关键字来屏蔽插入错误。如果,你的数据表里已经存在有将要插入的相同记录,那么这个时候就会产生插入报错,使用insert ignore into将绕开这个错误,继续下一条记录的插入。
如果你的表结构设计了唯一索引的话,那么使用replace into来更新你的数据表记录再好不过了,尤其是你在写一个脚本的时候,你就不需要在脚本里先查询数据表是否存在该条记录,如果存在,则更新;如果不存在则插入。你可以直接使用replace into,它会自动到数据表检测,如果在唯一索引字段存在该值,则会先删除该条记录,然后再插入新记录;如果唯一索引字段不存在该值,则直接插入数据表,简单且非常实用。
技巧三、给你的表建立唯一索引
很多情况下建立表唯一索引,能给我们省下很多麻烦事,想上面说的replace into就必须是有唯一索引,当然,很多人喜欢用到的自增主键ID尽管也是唯一索引,我建议是能在其他经常出现在where条件屁股后面的字段,并且值是唯一的话可以加个索引甚至唯一索引,查询速度将会提高很多。
说在最后:当然,insert ignore into与replace into大家要根据自己的需求来确定,不一定就适合你现在的需求;我只是提示大家还有这么些个实用的小知识大家可以灵活运用到项目里边。
读写锁和互斥量(互斥锁)很类似,是另一种线程同步机制,但不属于POSIX标准,可以用来同步同一进程中的各个线程。当然如果一个读写锁存放在多个进程共享的某个内存区中,那么还可以用来进行进程间的同步,
和互斥量不同的是:互斥量会把试图进入已保护的临界区的线程都阻塞;然而读写锁会视当前进入临界区的线程和请求进入临界区的线程的属性来判断是否允许线程进入。
相对互斥量只有加锁和不加锁两种状态,读写锁有三种状态:读模式下的加锁,写模式下的加锁,不加锁。
读写锁的使用规则:
● 只要没有写模式下的加锁,任意线程都可以进行读模式下的加锁;
● 只有读写锁处于不加锁状态时,才能进行写模式下的加锁;
读写锁也称为共享-独占(shared-exclusive)锁,当读写锁以读模式加锁时,它是以共享模式锁住,当以写模式加锁时,它是以独占模式锁住。读写锁非常适合读数据的频率远大于写数据的频率从的应用中。这样可以在任何时刻运行多个读线程并发的执行,给程序带来了更高的并发度。
需要提到的是:读写锁到目前为止仍然不是属于POSIX标准,本文讨论的读写锁函数都是有Open Group定义的的。例如下面是在我机器上,编译器是gcc version 4.4.6,关于读写锁的定义是包含在预处理命令中的:
#if defined __USE_UNIX98 || defined __USE_XOPEN2K ... 读写锁相关函数声明... #endif |
1、读写锁的初始化和销毁
/* Initialize read-write lock */ int pthread_rwlock_init (pthread_rwlock_t *__restrict __rwlock, __const pthread_rwlockattr_t *__restrict __attr); /* Destroy read-write lock */ extern int pthread_rwlock_destroy (pthread_rwlock_t *__rwlock); 返回值:成功返回0,否则返回错误代码 |
上面两个函数分别由于读写锁的初始化和销毁。和互斥量,条件变量一样,如果读写锁是静态分配的,可以通过常量进行初始化,如下:
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; |
也可以通过pthread_rwlock_init()进行初始化。对于动态分配的读写锁由于不能直接赋值进行初始化,只能通过这种方式进行初始化。pthread_rwlock_init()第二个参数是读写锁的属性,如果采用默认属性,可以传入空指针NULL。
那么当不在需要使用时及释放(自动或者手动)读写锁占用的内存之前,需要调用pthread_rwlock_destroy()进行销毁读写锁占用的资源。
2、互斥锁的属性设置
/* 初始化读写锁属性对象 */ int pthread_rwlockattr_init (pthread_rwlockattr_t *__attr); /* 销毁读写锁属性对象 */ int pthread_rwlockattr_destroy (pthread_rwlockattr_t *__attr); /* 获取读写锁属性对象在进程间共享与否的标识*/ int pthread_rwlockattr_getpshared (__const pthread_rwlockattr_t * __restrict __attr, int *__restrict __pshared); /* 设置读写锁属性对象,标识在进程间共享与否 */ int pthread_rwlockattr_setpshared (pthread_rwlockattr_t *__attr, int __pshared); 返回值:成功返回0,否则返回错误代码 |
这个属性设置和互斥量的基本一样,具体可以参考互斥量的设置互斥量的属性设置
3、互斥锁的使用
/* 读模式下加锁 */ int pthread_rwlock_rdlock (pthread_rwlock_t *__rwlock); /* 非阻塞的读模式下加锁 */ int pthread_rwlock_tryrdlock (pthread_rwlock_t *__rwlock); # ifdef __USE_XOPEN2K /* 限时等待的读模式加锁 */ int pthread_rwlock_timedrdlock (pthread_rwlock_t *__restrict __rwlock, __const struct timespec *__restrict __abstime); # endif /* 写模式下加锁 */ int pthread_rwlock_wrlock (pthread_rwlock_t *__rwlock); /* 非阻塞的写模式下加锁 */ int pthread_rwlock_trywrlock (pthread_rwlock_t *__rwlock); # ifdef __USE_XOPEN2K /* 限时等待的写模式加锁 */ int pthread_rwlock_timedwrlock (pthread_rwlock_t *__restrict __rwlock, __const struct timespec *__restrict __abstime); # endif /* 解锁 */ int pthread_rwlock_unlock (pthread_rwlock_t *__rwlock); 返回值:成功返回0,否则返回错误代码 |
(1)pthread_rwlock_rdlock()系列函数
pthread_rwlock_rdlock()用于以读模式即共享模式获取读写锁,如果读写锁已经被某个线程以读模式占用,那么调用线程就被阻塞。在实现读写锁的时候可以对共享模式下锁的数量进行限制(目前不知如何限制)。
pthread_rwlock_tryrdlock()和pthread_rwlock_rdlock()的唯一区别就是,在无法获取读写锁的时候,调用线程不会阻塞,会立即返回,并返回错误代码EBUSY。
pthread_rwlock_timedrdlock()是限时等待读模式加锁,时间参数struct timespec * __restrict __abstime也是绝对时间,和条件变量的pthread_cond_timedwait()使用基本一致,具体可以参考pthread_cond_timedwait() 3条件变量的使用
(2)pthread_rwlock_wrlock()系列函数
pthread_rwlock_wrlock()用于写模式即独占模式获取读写锁,如果读写锁已经被其他线程占用,不论是以共享模式还是独占模式占用,调用线程都会进入阻塞状态。
pthread_rwlock_trywrlock()在无法获取读写锁的时候,调用线程不会进入睡眠,会立即返回,并返回错误代码EBUSY。
pthread_rwlock_timedwrlock()是限时等待写模式加锁,也和条件变量的pthread_cond_timedwait()使用基本一致,具体可以参考pthread_cond_timedwait()3条件变量的使用。
(3)pthread_rwlock_unlock()
无论以共享模式还是独占模式获得的读写锁,都可以通过调用pthread_rwlock_unlock()函数进行释放该读写锁。
下面是测试代码:
#include <iostream> #include <cstdlib> #include <unistd.h> #include <pthread.h> using namespace std; struct{ pthread_rwlock_t rwlock; int product; }sharedData = {PTHREAD_RWLOCK_INITIALIZER, 0}; void * produce(void *ptr) { for (int i = 0; i < 5; ++i) { pthread_rwlock_wrlock(&sharedData.rwlock); sharedData.product = i; pthread_rwlock_unlock(&sharedData.rwlock); sleep(1); } } void * consume1(void *ptr) { for (int i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); cout<<"consume1:"<<sharedData.product<<endl; pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } void * consume2(void *ptr) { for (int i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); cout<<"consume2:"<<sharedData.product<<endl; pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } int main() { pthread_t tid1, tid2, tid3; pthread_create(&tid1, NULL, produce, NULL); pthread_create(&tid2, NULL, consume1, NULL); pthread_create(&tid3, NULL, consume2, NULL); void *retVal; pthread_join(tid1, &retVal); pthread_join(tid2, &retVal); pthread_join(tid3, &retVal); return 0; } |
测试结果如下:
consume1:0 consume2:0 consume2:0 consume1:1 consume2:1 consume1:2 consume2:2 consume1:3 consume2:3 consume1:4 |
如果把consume1的解锁注释掉,如下:
void * consume1(void *ptr) { for (int i = 0; i < 5;) { pthread_rwlock_rdlock(&sharedData.rwlock); cout<<"consume1:"<<sharedData.product<<endl; //pthread_rwlock_unlock(&sharedData.rwlock); ++i; sleep(1); } } |
程序的执行结果如下:
consume1:0 consume2:0 consume2:0 consume1:0 consume2:0 consume1:0 consume2:0 consume1:0 consume2:0 consume1:0 |
从执行结果可以看出Open Group提供的读写锁函数是优先考虑等待读模式占用锁的线程,这种实现的一个很大缺陷就是出现写入线程饿死的情况。
④ 性能监控和调整
在有多个数据库服务器的大型广域分布式管理信息系统中,客户/服务器及网络环境的管理上升到一个非常重要的位置,这直接关系到系统的效率和可靠性。
⑤ 系统性能与并发控制
系统含有多个基本数据库,系统的数据量和用户数都十分庞大,故要求数据库服务器具有极强的联机事务处理能力和优越的性能,同时,对数据库和日志提供高速的备分机制。
此外,SQL数据库服务器应能自动控制并行机制,以保证数据的一致性,提供合理、高效的封锁机制,并能自动检测和解决用户之间的死锁。
⑥ 事务的完整性和恢复
数据库服务器必须具有事务完整性机制,如日志文件、回退及向前恢复,并要求能从各种异常情况下恢复数据。在日常工作中能够联机地备份数据库和日志,以保证在系统可以24小时联机运行。在数据安全性方面支持磁盘镜像,在处理机的可靠性上支持双机环境。
⑦ 分布式处理
数据库必须支持分布式环境中节点自治的原则,以保证数据的分布管理和完整性;对用户提供分布式透明以便于应用系统的使用。在分布式查询和更新上必须能执行远过程调用(RPC)以减少网络开销。另外,数据库系统还应支持数据复制,并能对复制的数据进行自动刷新。在分布式更新操作中还应具有两阶段提交的机制。
⑧ 应用开发
数据库服务所支持的宿主语言应包括C、JAVA等常用高级语言,并以预编译式函数调用的方式访问数据库。在系统的设计开发中,工具的使用十分重要。因此,数据库系统必须能够提供足够的工具供开发者和用户自由选择;这些工具应涉及数据库分析设计,应用开发,调试和运行监控等各个不同的阶段。
⑨ 出现突发热点,造成数据库读写访问剧增,受限于部门数据库资源机器,而错失扩大业务的良机。
⑩ 缺乏统一的数据库服务器性能监控和报警。
⑾ 无专门的人进行全局数据库各种读写操作统计的分析
3、测试运行
可靠性测试运行即在真实的测试环境中或可靠性仿真测试环境中,运行应用系统同时用测试用例对软件进行严格的测试。
4、可靠性测试数据分析
经过数据和数据收集、 测试环境的准备、 测试运行后,必须要进行分析。可靠性测试数据分析主要是对收集的数据、运行的结果进行分析。根据运行结果判断软件是否实用和可靠?是否达到设计的要求?
软件可靠性测试的执行流程大致包括可靠性测试所需数据和数据收集、 测试环境的准备、 测试运行、可靠性测试数据分析几个部分。
1、可靠性测试所需数据和数据收集
可靠性测试所需的数据应根据系统需求说明书、系统概要设计说明书、系统功能设计说明书的要求去选择所需要的测试数据。数据应包括输入的数据(从外部输入的数据)和应用系统运行过程中新产生的数据。
为了验证系统的可靠性,需要对可靠性测试所需的数据进行总结和归纳,要求对输入的数据和运行过程中产生的数据进行分类汇总,找出影响系统运行可靠性的关键点。
2、可靠性测试环境的准备
可靠性测试没有环境是不行的,为了保障可靠性测试能够顺利的进行,就需要对测试环境进行准备,环境准备主要表现为5大平台。
(1)硬件环境平台
硬件环境在前面已指出(服务器、路由器、交换机、防火墙、磁盘阵列),这里就不再叙述了。
(2)网络基础平台
网络基础平台包括网络传输、接入系统、操作系统和硬件环境平台等。
网络基础平台是计算机网络的枢纽,由传输设备、交换设备、网络接入设备、布线系统、网络服务器和操作系统、数据存储和系统等组成,如图所示。
图 网络基础平台的组成
(3)网络服务平台
网络服务平台的功能和性能要素主要包括:
★ 电子邮件服务;
★ DNS服务器;
★ WWW服务器;
★ 多媒体业务网络系统;
★ 视频会议系统。
(4)网络安全平台
网络安全平台主要包括:
★ 支持多种平台的病毒防范。
★ 支持对服务器的病毒防治。
★ 支持对电子邮件附件的病毒防治。
★ 提供对病毒特征信息和检测引擎的定期在线更新服务。
★ 实现集中管理、分布式杀毒。
★ 进行系统数据收集,进行统一存储,集中进行安全审计。
★ Web信息防功能。
★ 具备自动监控,自动恢复,自动报警。
★ 提供日志管理、扫描策略管理、更新管理。
★ 能够对外网与外网之间交换数据进行基于数据内容的过滤。
(5)网络管理平台。
随着计算机网络的规模越来越大,设备越来越多,必须使用专门的网络管理系统来管理、监测和控制网络的运行。网络管理平台的功能和性能要素主要包括:
① 能够进行全网范围内的统一管理,包括制定统一的管理模式和策略,对资源的统一分配和调度;能够对网络内部各种平台、数据库、网络应用的运行状态进行有效监控;
② 能够进行高度的自动化管理,尽量减少人为干预,避免由于人员操作不当引起的系统故障;
★ 可以对网络节点进行远程配置,并能实时监控各节点的性能状态,一旦出现故障便能自动及时报警;
★ 能够提供辅助支持,出现网络故障时可以快速响应,同时为系统的长期规划提供统计依据;
★ 尽量减少管理信息对网络传输的压力。
★ 网络管理:对整个网络和指定子系统或设备的工作状态进行集中管理和监控,包括拓扑结构、网络设备、连通状态、故障分析等内容。
★ 系统管理:服务器系统、存储和备份系统、网络服务、网络安全系统进行统一的管理和监控;
★ 运行维护管理:对网络系统各种资源的运行状况进行全面的信息采集和自动预警。
(6)数据库平台
数据库是MIS信息系统中的重要支持技术,如何选择数据库管理系统是一个重要的问题。要选择出一种合适的数据库,就必须从数据库应用的特点和可靠性角度出发,测试考虑的因素主要有:
① 符合关系型的标准
网络上运行的数据库必须是基于ANSI SQL标准的分布式关系数据库,各节点均有自己的RDBMS核心。
② 数据库系统的体系结构
数据库系统应该是基于客户/服务器体系结构的分布式数据库。用户的应用程序可与数据库服务器运行在不同的硬件平台上,从而充分发挥不同类型硬件的特长。同时,在分布式环境应具有与异种数据库的互操作性。
③ 良好的系统可扩展性
随着业务的发展,管理信息系统计算机网络中可能增加新的SQL服务或其他专用服务器及数据源,也可能将现有的服务器升级或增加客户机。因此,数据库系统必须能提供良好的可扩展性,充分地保护用户过去、现在和将来的投资。
敏捷开发的度量
敏捷开发是一种以人为核心、迭代、循序渐进的开发方法。在敏捷开发中,软件项目的构建被切分成多个子项目,各个子项目的成果都经过测试,具备集成和可运行的特征。
换言之,就是把一个大项目分为多个相互联系,但也可独立运行的小项目,并分别完成,在此过程中软件一直处于可使用状态。
度量的概念
根据一定的规则,将数字或符号赋与系统、构件、过程等实体的特定属性,从而使我们能清晰地理解该实体及其属性。
简而言之,度量就是对事物属性的量化表示。
度量的目的
度量的根本目的是通过量化的分析和综合,帮助我们提高生产率,提高产品质量,降低研发成本、维护成本和产品研发周期,提高用户满意度,为组织持续改进提供量化的指标和反馈。
度量本身不是目的,而是手段。
度量的过程定义
测试的度量
● 多纬度的测试度量一
测试广度的度量指所有需求中有多少需求在某一时刻已测试,从而度量测试计划执行、测试进度等状态。
事儿一:
基于功能和性能测试覆盖评测是对被测试的功能和非功能点的覆盖率分析,是根据测试已经执行的功能点的多少来表示的。这种测试覆盖策略类型广泛的用于各个行业,产品的测试度量中。
其中非功能点包括性能,压力,易用性,环保,兼容性……
事儿二:
基于代码的测试覆盖评测是对被测试的程序代码语句、路径或条件的覆盖率分析,是根据测试已经执行的源代码的多少来表示的。这种测试覆盖策略类型对于安全至上的系统来说非常重要。
代码覆盖可以建立在控制流(语句、分支或路径)或数据流的基础上。控制流覆盖的目的是测试代码行、分支条件、代码中的路径或软件控制流的其他元素。数据流覆盖的目的是通过软件操作测试数据状态是否有效。
● 多纬度的测试度量二
测试深度的度量是指被测试覆盖的独立基本路径占程序中基本路径的总数的比值。基本路径数目的度量可以用McCabe环形计算复杂度方法来计算。
事儿一:
测试用例的深度、质量和有效性。
测试用例的深度(Test Case Depth)度量可以表示为每KLOC的测试用例数或每个功能点/对象点的测试用例数。
测试用例的质量(Test Case Quality)可以用由测试用例发现的缺陷数量来度量, 即TCQ = 测试用例发现的缺陷数量/总的缺陷数量
测试用例的效率可以用每100或1000个测试用例所发现的缺陷数来衡量。
● 多纬度的测试度量三
测试用例的度量,除了前面提到的覆盖率和深度。还有重要的度量参数是测试用例的执行率,通过率和测试用例的执行质量和效率。
事儿一:
测试执行的质量一般可以用于不同测试阶段给下一测试阶段所遗留的软件缺陷和总缺陷数的比值来衡量,一般要求低于0.5%。
测试执行效率可以用下列几种方法来综合度量:
* 每个人日所执行的测试用例数
* 每个人日所发现的缺陷数
* 每修改KLOC所运行的测试用例数
事儿二:
测试用例的执行率是指所有测试用例已经执行的用例和总用例的比。
测试用例的通过率是指所有执行并通过的用例和总用例的比。
这两个参数不但能反应最总的测试质量而且通过过程的数据记录可以反应测试过程中的测试进度和测试效率等。
● 多纬度的测试度量四
相关缺陷的度量
.BUG数量
.BUG级别统计
.BUG分布统计(模块)
.BUG分布统计(阶段)
.BUG密度
.BUG关闭率
.BUG状态统计
事儿一:
事儿二:
事儿三:
● 多纬度的测试度量五
其它相关度量:测试规模度量,人员效率素质度量,项目偏移量度量,工作偏移量度量,测试用例密度度量,返工成本度量……
.实事儿
世界500强工业控制类公司,研发生产流程中对测试的度量。测试管理工具为Quality Center,Bug管理工具为Clear Quest。
测试覆盖率
测试覆盖率是指测试用例对需求的覆盖情况。
计算公式:已设计测试用例的需求数/需求总数。测试覆盖率从纬度上说包括广度覆盖和深度覆盖;从内容上说包括用户场景覆盖、功能覆盖、功能组合覆盖、系统场景覆盖。
测试执行通过率
测试执行通过率,指在实际执行的测试用例中,执行结果为“通过”的测试用例比率。
计算公式:执行结果为“通过”的测试用例数/实际执行的测试用例总数。
测试执行率
执行率,顾名思义,就是指实际执行过程中确定已经执行的测试用例比率。
计算公式:已执行的测试用例数/设计的总测试用例数。
未解决缺陷状态
缺陷未解决状态,指某个阶段所有缺陷中未解决的缺陷的数量。
未关闭缺陷包含缺陷严重级别和时间信息
。
质量管理的度量
.研发中心的质量管理
质量管理(quality management)是指确定质量方针、目标和职责,并通过质量体系中的质量策划、质量控制、质量保证和质量改进来使其实现的所有管理职能的全部活动。
QC的七大手法
事儿一:
风险管理的数据收集和分析
事儿二:
CR需求变更的统计和分析
事儿三:
Peer Review 相关数据的收集和分析。
其他度量:
里程碑管理度量
作业流程度量
控制度量
版本管理控制度量
个人能力成熟度度量
部门成熟度度量
……
什么是EP英文简称“Engine Productivity”直译:工程生产力,通俗点:提升研发生产力。我在第一篇中提到百度QA职责中有一部分是提升研发效率,而不仅是提升测试效率。这项职责不是一个日常工作中测试管理者刻意去强调的工作内容,而是因为QA角色的特点,在自己的日常工作中会参与产品的全流程研发环节,因此当发现流程中有可以提升效率的地方或是通过外部交流知道了可提升效率的技术后自发地去做的事。当然做好了自然会得到组织的认可,于是QA都有这个习惯会自发主动地去思考如何提升研发效率,除了帮QA提升工作效率,也帮产品开发者提升工作效率,帮产品经理提升工作效率,从而可以全流程的优化加快产品发布上市的速度。总体而言,QA主要通过各类自动化工程手段、研发流程的工程化改造、产品代码可测试性改造、测试环境工程化改造等实现从开发阶段、测试阶段、版本发布阶段三个领域同时进行研发效率提升的目标。
作为QA要提升研发效率最早也最容易想到的就是自动化一切提升测试效率,不仅是自动化回归功能测试,自动化性能测试,自动化测试框架、还演进出自动化找代码缺陷,自动化找出算法效果缺陷的数据集、自动化Code Review过程、自动化进行代码级的扫描准入(valgrind、vitamin、errhunter)、自动化定位Crash位置新的自动化工程手段。例如:在我目前服务的某产品线,有个QA每周都会分析一些bug的缺陷根因,然后抽取出Code Review规则给另一个QA来自动化实现后再提供给开发同学使用,减少开发同学Code Review的时间提升工作效率。当我们在测试过程中触发了crash,会有一个平台自动在1-3分钟内给出这个crash发生的代码行,这个crash之前的代码调用序列,大大减少了开发同学和QA分析原始log定位crash的时间成本,帮助开发同学快速定位和修复crash代码。除了本地运行的回归测试用例,质量部还提供了分布式并行测试平台帮助各产品加快大量其他功能回归测试任务执行的速度,缩短整体回归测试时间,自动化功能回归测试执行时间缩短为原来的几十分之一,为产品更快发布又节省了研发周期。
QA对公司研发效率的提升不只是自动化一切,还有其他的工程手段诸如:进行可测试性改造、单元测试效率提升、测试环境搭建与仿真。
可测试性改造的目的既有减少产品缺陷的产生的效果也有减少测试实现成本、发现问题可快速定位的效果。因此一些百度的高阶QA会和开发配合合作进行产品的可测试性改造以提升后期测试执行和开发定位问题的效率。首先:QA会给RD提供代码可测试性的方法论和设计准备,降低代码的圈复杂度,不但减少复杂逻辑引起的代码问题、还更有利于降低单元测试开展的复杂度从而提升单元测试覆盖率,预防软件缺陷的产生,就减少了后期定位和修复bug的成本,提升产品整体生产效率。其次:为了帮助后续RD更好的定位问题,以及QA更容易开展系统测试,QA还会提出一些可测试性设计理念给RD,希望能融入到产品设计中。例如我今年搞的一个可测试性框架,不但实现了测试代码不驻留产品代码,QA通过在测试阶段一键自动注入测试代码到产品代码中进行专项crash风险挖掘工作,而且挖掘出来的每个crash都是必现的,都能在1到5分钟内复现,都能直接给出是哪一行代码的发生的crash,是什么原因触发的crash。这样的可测试性改造不但大大提升了测试人员发现crash的效率和crash复现的效率,结合我们的crash自动分析平台还提升了开发定位和修复crash的效率,开发人员定位的时间可以缩小到分钟级。因此提升研发测试效率除了自动化一切,产品进行可测试性改造和设计也是减少bug定位和复现成本的重要EP手段之一。
在QA的EP支撑过程中,除了通过可测试性设计预防缺陷减少后期的研发成本消耗,在缺陷预防领域还大力支持RD更简单更高效地开展单元测试降低单元测试实施的成本和难度,提升单元测试开展的效率。支持的主要手段有:提供更简化使用的单元测试框架、单元测试的教练式辅导。
在日常的开发和测试工作中,无论开发人员还是测试人员都会相当比例的时间用在测试环境搭建、调试和环境问题的定位中,直接影响了研发工作效率。因此QA必须进行测试环境的EP工程改造,诸如:测试环境自动部署、测试环境仿真、开发测试模拟器的来降低相关的研发成本、加快测试工作速度和开发定位的效率。据我了解,几乎所有产品测试团队都实现了测试环境自动部署来提高研发工作效率,减少测试环境搭建和调试的成本。同时还通过测试环境仿真,让产品实际运行环境中的变量成为可控的因子来减少环境问题的定位成本,提升QA和开发在环境问题上的定位效率。在移动APP的测试中有成本很低的测试仿真环境来模拟低质量的移动网络,不但可仿真低质量网络的丢包率、时延和低速这些特征,还能仿真真实移动网络中手机被迫频繁退出和接入网络的效果(如坐地铁或走动的场景)。通过仿真的移动网络,我们不但可为用户提供更高可用性的产品而且还大大缩短了户外真正网络环境引起的产品问题效果的定位时间。以前认为是环境问题无法复现的问题都可以实现分钟级为单位快速复现,并根据仿真环境设置的参数快速定位产品问题根源。
在测试阶段除了自动化测试、可测试性工具、测试环境搭建和仿真外。各测试团队还积极结合产品特点开发一些测试辅助工具、进行测试设计模式和测试模式的改进,力求在不增加测试周期的条件下甚至减少测试周期的条件下可以尽早发现更多产品中存在的质量风险,提升测试效率。例如:2012年时很多产品组都积极应用探索测试方法,有些产品组在未增加测试时间和测试人力的情况下,通过应用探索测试方法新发现的bug数量占据总bug数量的30%以上,相比过去直接提升了40%以上的测试生产力。还有一些团队在回归测试范围领域进行测试技术探索精准回归测试,缩短回归测试工作量,提升了测试团队的工作效率。
以上内容是我在百度看到的比较普适性的在开发阶段和测试阶段提升工程效率的QA工程实践,代表了百度成熟的应用,不代表全部。
下一篇文章计划给大家分享百度QA在版本发布阶段进行的EP工作实践。
版权声明:本文出自 架构师Jack 的51Testing软件测试博客:http://www.51testing.com/?293557
原创作品,转载时请务必以超链接形式标明本文原始出处、作者信息和本声明,否则将追究法律责任。
相关链接:
《我眼中的百度QA》第一篇:百度QA的特点与核心价值
《我眼中的百度QA》第一篇:百度QA的特点与核心价值(下半部分)
《我眼中的百度QA》第二篇:百度用户体验提升的产品评测
概述:本文主要研究的是JAVA的字符串拼接的性能,原文中的测试代码在功能上并不等价,导致concat的测试意义不大。不过原作者在评论栏给了新的concat结果,如果有兴趣的同学建议自己修改代码测试。
原文出处:http://www.venishjoe.net/2009/11/java-string-concatenation-and.html
在JAVA中拼接两个字符串的最简便的方式就是使用操作符”+”了。如果你用”+”来连接固定长度的字符串,可能性能上会稍受影响,但是如果你是在 循环中来”+”多个串的话,性能将指数倍的下降。假设有一个字符串,我们将对这个字符串做大量循环拼接操作,使用”+”的话将得到最低的性能。但是究竟这 个性能有多差?如果我们同时也把StringBuffer,StringBuilder或String.concat()放入性能测试中,结果又会如何 呢?本文将会就这些问题给出一个答案!
我们将使用Per4j来计算性能,因为这个工具可以给我们一个完整的性能指标集合,比如最小,最大耗时,统计时间段的标准偏差等。在测试代码中,为了得到一个准确的标准偏差值,我们将执行20个拼接”*”50,000次的测试。下面是我们将使用到的拼接字符串的方法:
● Concatenation Operator (+)
● String concat method – concat(String str)
● StringBuffer append method – append(String str)
● StringBuilder append method – append(String str)
最后,我们将看看字节码,来研究这些方法到底是如何执行的。现在,让我们先开始来创建我扪的类。注意为了计算每个循环的性能,代码中的每段测试代码都需要用Per4J库进行封装。首先我们先定义迭代次数
private static final int OUTER_ITERATION=20; private static final int INNER_ITERATION=50000; |
接下来,我们将使用上述4个方法来实现我们的测试代码。
String addTestStr = "" ;
String concatTestStr = "" ;
StringBuffer concatTestSb = null ;
StringBuilder concatTestSbu = null ;
for ( int outerIndex= 0 ;outerIndex<=OUTER_ITERATION;outerIndex++) {
StopWatch stopWatch = new LoggingStopWatch( "StringAddConcat" );
addTestStr = "" ;
for ( int innerIndex= 0 ;innerIndex<=INNER_ITERATION;innerIndex++)
addTestStr += "*" ;
stopWatch.stop();
}
for ( int outerIndex= 0 ;outerIndex<=OUTER_ITERATION;outerIndex++) {
StopWatch stopWatch = new LoggingStopWatch( "StringConcat" );
concatTestStr = "" ;
for ( int innerIndex= 0 ;innerIndex<=INNER_ITERATION;innerIndex++)
concatTestStr.concat( "*" );
stopWatch.stop();
}
for ( int outerIndex= 0 ;outerIndex<=OUTER_ITERATION;outerIndex++) {
StopWatch stopWatch = new LoggingStopWatch( "StringBufferConcat" );
concatTestSb = new StringBuffer();
for ( int innerIndex= 0 ;innerIndex<=INNER_ITERATION;innerIndex++)
concatTestSb.append( "*" );
stopWatch.stop();
}
for ( int outerIndex= 0 ;outerIndex<=OUTER_ITERATION;outerIndex++) {
StopWatch stopWatch = new LoggingStopWatch( "StringBuilderConcat" );
concatTestSbu = new StringBuilder();
for ( int innerIndex= 0 ;innerIndex<=INNER_ITERATION;innerIndex++)
concatTestSbu.append( "*" );
stopWatch.stop();
}
|
|
接下来通过运行程序来生成性能指标。我的运行环境是64位的Windown7操作系统,32位的JVM(7-ea) 带4GB内存,双核Quad 2.00GHz的CPU的机器。
经过20次迭代后,我们得到如下的数据:
结果非常完美如我们想象的那样。唯一比较有趣的事情是为什么String.concat也很不错,我们都知道,String是一个常类(初始化后就不会改变的类),那么为什么concat的性能会更好一些呢。(译者注:其实原文作者的测试代码有问题,对于concat()方法的测试代码应该写成 concatTestStr=concatTestStr.concat(“*”)才对。)为了回答这个问题,我们应该看看concat反编译出来的字节 码。在本文的下载包里面包含了所有的字节码,但是现在我们先看一下concat的这个代码片段:
46 : new # 6 ; //class java/lang/StringBuilder
49 : dup
50 : invokespecial # 7 ; //Method java/lang/StringBuilder."<init>":()V
53 : aload_1
54 : invokevirtual # 8 ; //Method java/lang/StringBuilder.append:
(Ljava/lang/String;)Ljava/lang/StringBuilder;
57 : ldc # 9 ; //String *
59 : invokevirtual # 8 ; //Method java/lang/StringBuilder.append:
(Ljava/lang/String;)Ljava/lang/StringBuilder;
62 : invokevirtual # 10 ; //Method java/lang/StringBuilder.toString:()
Ljava/lang/String;
65 : astore_1
66 : iinc 7 , 1
69 : goto 38
|
这段代码是String.concat()的字节码,从这段代码中,我们可以清楚的看到,concat()方法使用了 StringBuilder,concat()的性能应该和StringBuilder的一样好,但是由于额外的创建StringBuilder和 做.append(str).append(str).toString()的操作,使得concate的性能会受到一些影响,所以 StringBuilder和String Cancate的时间是1.8和3.3。
因此,即时在做最简单的拼接时,如果我们不想创建StringBuffer或StringBuilder实例使,我们也因该使用concat。但是对于大量的字符串拼接操作,我们就不应该使用concat(译者注:因为测试代码功能上并不完全等价,更换后的测试代码concat的平均处理时间是1650.9毫秒。这个结果在原文的评论里面。),因为concat会降低 你程序的性能,消耗你的cpu。因此,在不考虑线程安全和同步的情况下,为了获得最高的性能,我们应尽量使用StringBuilder
译文链接:http://coolshell.cn/articles/2235.html