我的新浪微博:http://weibo.com/u/1602714773 CSDN博客:http://blog.csdn.net/hunterno4

spoon+robotium+jenkins进行自动化持续回归测试

上一篇 / 下一篇  2015-02-07 20:10:46 / 个人分类:android自动化测试

自动化测试的意义
        别说是外行人,即使是正在从事自动化测试工作的人来说,现在或曾经都或多或少有过这样的疑惑,辛苦写了自动化测试用例,却基本发现不了问题,其意义何在?在说明这个意义前先看下质量的定义。

质量的定义:
        维基百科中对于品质(Quality)的定义:中国大陆亦称为“质量”,可指物品的特征、品性、本质,也可指商品或服务的水准、质量。
影响品质的要素包括物品的可靠性、安全性,功能上是否完备,能否满足需求, 等等。
        对于软件质量的定义:软件质量,是指软件系统或系统中的软件部分的质量,即满足用户需求,包括功能需求和性能需求的程度。软件的质量包括功能、性能、可靠性、安全性、可升级性、可维护性、其他质量特性,这也是我们一般认识中的质量,从而也指导着大多数的质量相关的测试工作
        从以上的定义中也可以看出,对于质量,大家的认知更多的都是在“质”上,而较少地考虑到了“量”上,质量质量应该是质与量的结合,既关注“质”即品质保证,也要关注“量”即效率。随着互联网的高速发展,特别是随着移动互联网的到来,企业希望能够更快地响应市场变化且又希望产品的品质也能够有所保障,因此各类软件项目提速提质相关的如敏捷、持续集成、持续交付、自动化等等技术就诞生了。也就是说对于质量,我们的目的不再仅仅是品质,而是真正的“质量”,即Move fast and don’t break things!
        知晓了质量的本质与目的后,再来看前面的问题就好解释多了,自动化测试较手工测试来说,确实远没有手工测试更能发现问题,但正如前文所述质量的意义除了在于“质”外,还有“量”,因此,自动化测试更大的意义在于“量”上,在于提高效率上,且实际项目中,自动化也不仅仅只是把测试自动化,但凡能提高效率且适合自动化的均应该进行自动化。当然了,理论上自动化测试还是应该要能发现20%左右的问题的,对于一点问题都发现不了的自动化测试,则需要好好思考测试用例设计、测试框架搭建方面的问题了。如何更好、更快、更稳定、更可靠地进行测试自动化,这也即是本文所要分享的。在进入正题前,先看看质量的意义。

质量的意义:
对于用户,质量更多的就意味着软件品质:
        试想这么一个例子:某天,大明正在梦乡中与吃着美味的烤鸭,突然被震耳的手机闹铃吵醒了,大明前一晚由于知道明天早上有重要会议,所以特地将手机闹铃调早了半个小时,但也正因为如此,手机闹铃软件出了点故障,比原定时间迟了半小时才响。。大明只好匆匆忙忙顾不上早餐就出门赶去上班了,且赶紧用打车软件叫了辆车,大明心想应该还来得急,就耐心地等着**车的到来,在瑟瑟寒风转眼间就过去10分钟了,大明开始急了,**车怎么还不来。。又过了10分钟,**车终于出现,原来新手**车司机由于不认识大明的住处,因此使用了车上的导航,结果由于导航系统未及时更新地图数据,司机开往了错误的地方。。意料之中的,因为没能及时参加会议,大明挨了一顿批。快到中午了,由于心情不佳不想出去吃钣了,大明用外卖软件叫了个外卖,已经饥肠辘辘的大明塞上耳机听着音乐希望可以放松些,结果音乐中间莫名中断过好几次,心情反而更糟了。。。至于下午的事就不说了,最终大明度过了郁闷的一天。
        虽然以上的大明略显悲惨,但也决不是没可能发生的,随着移动互联网的到来,每个人的生活越来越需要手机及其上的软件,每个人的幸福度很大程度地受各种软件的影响,如果所有的软件都能很好地运行,这个世界将变得多么美好~

对于企业,质量则将意味着真正的“质量”:
        随着信息的不断爆炸式增长,我们这个时代的节奏也越来越快,特别是移动互联网,一个软件的项目周期再也不能像传统软件那样半年甚至好几年。试想,如果你的软件项目的持续交付能力比竞争对手弱,那么将意味着对方可以更快地响应市场变化、可以比你更快地将创意付诸实现,那么很显然地,你将失去市场,且在移动互联网,在这种情况下往往花费巨大的营销运营代价都无法挽回颓势。因此,提高软件项目的整体效率势在必行,且需要时时刻刻地去思考如何才能加快项目的运转速度、提高持续交付产品的能力并且还能更有效地保障产品的质量。
        “让天下人都能用上更好的软件”,想必这应该是质量、测试人员,甚至应该是所有软件相关从业者的任务与使命~
        基于以上背景结合实际实践,转入《spoon+robotium+jenkins进行自动化持续回归测试》正题。。

自动化测试的原因:
        例如敏捷测试中所提到的,手动测试需要太长的时间、手动过程容易出错、自动化让人们有时间做更有价值的工作、自动化的回归测试提供了安全网、自动化测试能较早且频繁地提供反馈、测试提供文档等等。
        简而言之,自动化测试可以提高测试效率,将重复的活动自动化后可以有更多的时间去做更多有创造性、更需要人脑思考的探索性测试上。
        基于UI自动化测试的必要性:对于软件,即使编写了单元测试用例、组件测试用例,但这些测试并不是将软件组合成一个整体进行集成测试的,因此很难发现最接近于用户使用的集成测试问题,况且,很多项目团队压根就没有单元测试。对于android来说,系统版本碎片化较为严重,编写基于UI的自动化测试用例,还可以用来进行兼容性测试。

app自动化测试应该包含以下几个基本能力:
多机并行执行:同时在多台手机中执行自动化测试用例,以便在不同版本的Android操作系统中进行回归测试、兼容性测试
出错重试及出错截图:当用例执行未通过时,可以自动进行重跑并截图记录,以便减少因偶然因素导致用例执行未通过的情况
实时日志记录:对于测试执行过程应该能记录运行时的日志,以便详细发解测试执行情况
跨应用的能力:能够测试包含跨应用的诸多情况
生成Junit形式测试报告:生成详细的Junit形式的测试报告,可方便查看测试用例执行结果
代码覆盖率报告:生成代码覆盖率报告,以便进一步指导测试策略
持续快速反馈的能力:对于测试运行情况,应该要能够快速反馈
易于访问的报告:能够很方便地访问到测试报告详情

spoon介绍
项目地址:https://github.com/square/spoon
该项目的主要目的在于将能够将基于instrumentation的测试用例分发到各个不同的手机上,执行并将测试结果收集起来,生成最终的HTML总结报告。
将项目下载下来后,进入spoon/website/sample目录,访问index.html即可看到示例如下:

点击查看后有截图展示功能、运行时日志展示功能:


生成报告的目录结构如下:

其中junit-reports收集有junit格式的xml报告,通过jenkins的junit插件可以很方便生成单元测试报告
简单执行命令如下:

java -jar spoon-runner-1.1.1-jar-with-dependencies.jar \
    --apk example-app.apk \
    --test-apk example-tests.apk

详细参数:

Options:
    --apk              Application APK
    --fail-on-failure  Non-zero exit code on failure
    --output            Output path
    --sdk              Path to Android SDK
    --test-apk          Test application APK
    --title            Execution title
    --class-name        Test class name to run (fully-qualified)
    --method-name      Test method name to run (must also use --class-name)
    --no-animations    Disable animated gif generation
    --size              Only run test methods annotated by testSize (small, medium, large)
    --adb-timeout      Set maximum execution time per test in seconds (10min default)

注:
1.由于spoon报告中的静态页面中使用的是googleapis中的在线字体,因此报告可能打开会相当慢
        <link href="http://fonts.googleapis.com/css?family=Roboto:regular,medium,thin,italic,mediumitalic,bold" rel="stylesheet">
因此实际要做为报告时,建议翻墙后访问http://fonts.googleapis.com/cssfamily=Roboto:regular,medium,thin,italic,mediumitalic,bold,将字体文件下载下来保存至文件例如放至static/fonts.css,然后静态引用<link href="static/fonts.css" rel="stylesheet">

2.spoon中未提供开启代码覆盖率的Option
        spoon由于只是封装** instrument命令,最终执行的其实还是am instrument执行用例的命令,因此是可以开启代码覆盖率功能的,要想增加-e coverage true选项使其支持收集代码覆盖率,则需要修改spoon源码,最方便的那就是在源码里写死选项了
SpoonDeviceRunner.java中增加如下两行写死参数:
  1. logDebug(debug, "About to actually run tests for [%s]", serial);  
  2. junitReport = FileUtils.getFile(output, JUNIT_DIR, serial + "_" + i + "_" +".xml");  
  3. runner = new RemoteAndroidTestRunner(testPackage, testRunner, device);  
  4. runner.setCoverage(true);//注:set为true后,被测应用打包时需要插桩才能生成ec代码覆盖率统计文件  
  5. runner.addInstrumentationArg("coverageFile""/sdcard/robotium/spoon" + i + ".ec");  
  6. runner.setMaxtimeToOutputResponse(adbTimeout);  
这样,当测试完成后,就会在sd卡中生成emma用于覆盖率统计的ec文件,将ec文件pull到CI服务器上
java -cp /usr/local/program/emma/emma.jar emma report -sp $source_path -r xml -in coverage.em,$ec_files
生成相应的xml文件后,结合jenkins中的相应插件就可以生成覆盖率报告了。


3.spoon不能将测试用例集分开执行

adb shell am instrument -w -e class com.android.foo.FooTest,com.android.foo.TooTest com.android.foo/android.test.InstrumentationTestRunner 

        如上,我们知道通过am instrument执行用例时,可以指定多个用例集,当被测试应用启动后,所有接下来要执行的用例集都运行在同一个进程中,如果在执行FooTest时,被测应用crash了,那么TooTest 将不再执行,你所收集到的测试结果也就不含TooTest的结果了。当你的用例集较多时,显然希望每次回归测试时能尽量把所有用例均执行过,而不希望因前面一个用例crash导致后面所有用例都没有执行到。
这时分开执行将类似如下:
adb shell am instrument -w -e class com.android.foo.FooTest com.android.foo/android.test.InstrumentationTestRunner
adb shell am instrument -w -e class com.android.foo.TooTest com.android.foo/android.test.InstrumentationTestRunner
想要让spoon支持这种方式运行只能修改spoon源码了
      对于以上几点不好的地方,博主fork了源码做了部分修改,请见:https://github.com/hunterno4/spoon
        至此,通过spoon即可完成多机并行执行实时日志记录生成Junit形式测试报告、收集代码覆盖率报告等功能了

出错重试及出错截图
        对于自动化测试用例,如果只是执行一次,常常因为偶然因素导致用例不能通过,如果每次收到的测试报告都是因为测试用例执行问题,显然这样的报告很快就不会有人愿意看了。
出错重试一般性做法:
1.收集测试执行时的控制台输出,然后分析输出是否包含某些指定关键字来判断是否失败了,然后进行重跑,这种方式是比较繁琐的。
2.根据Android junit自带的@FlakyTest实现的机制,重写runTest
重写runTest()方法可以参考http://qa.baidu.com/blog/?p=985
注:
1) 重写runTest时,需要注意的是,测试用例的执行流程如下:
setUp()——> runTest()——> tearDown()
这里的重跑只是重复执行test*()方法,因此对于许多跨Activity的测试用例,例如从Activity A跳转至Activity B,但用例在Activity B中断言失败了,此时重跑时,会继续执行test***()方法中点击跳转到B的那块代码,但此时界面还处于B中,显然是找不到A中的那些控件的,因此在重写runTest()方法时,需要在super.runTest()前自动地跳转到最初的Activity。这个可以通过solo.goBackToActivity()完成
2)在失败截图时,我们也常常不只希望就只在断言出错时截那一张图,而是希望能截取一系列运行过程的图,这可以通过solo.startScreenshotSequence()方法完成
实现方式如下:
  1. @Override  
  2.    protected void runTest() throws Throwable {  
  3.       
  4.     String testMethodName = getName();  
  5.     String currentTestClass = getClass().getName();  
  6.     LogUtils.logD(TAG, "currentTestClass:" + currentTestClass);  
  7.     boolean isScreenShot = true;  
  8.     boolean isScreenShotWhenPass = false;  
  9.     long startTime = 0;  
  10.     long endTime = 0;  
  11.     Holo holo = new Holo(getInstrumentation(), getActivity());//这里的holo是经过增删后的robotium,理解为solo即可  
  12.     String currentActivity = getActivity().getClass().getSimpleName();  
  13.     LogUtils.logD(TAG, "currentActivity:" + currentActivity);  
  14.     Method method = getClass().getMethod(getName(), (Class[]) null);             
  15.       
  16.     int retrytime = 3;  
  17.     if (method.isAnnotationPresent(RetryTest.class)) {  
  18.         retrytime = method.getAnnotation(RetryTest.class).retrytime();  
  19.         isScreenShot = method.getAnnotation(RetryTest.class).isScreenShot();  
  20.        }   
  21.     LogUtils.logD(TAG, "isScreenShot:" + isScreenShot);  
  22.       
  23.     int runCount = 0;  
  24.       
  25.     do {  
  26.         LogUtils.logD(TAG, "runCount:" + runCount);  
  27.         try {  
  28.             holo.goBackToActivity(currentActivity);  
  29.             if(runCount > 0){//当用例第一遍执行未通过后,开启截图序列,至于要截多少张图,可以根据实际情况来设计  
  30.                 holo.stopScreenshotSequence();  
  31.                 holo.startScreenshotSequence(endTime, 5, testMethodName, currentTestClass);  
  32.             }  
  33.             startTime = SystemClock.uptimeMillis();  
  34.             super.runTest();  
  35.             endTime = SystemClock.uptimeMillis() - startTime;  
  36.             LogUtils.logD(TAG, "run test" + testMethodName + ",testcase pass with time cost:" + endTime);  
  37.             if(isScreenShotWhenPass){  
  38.                 holo.takeSpoonScreenShot(testMethodName,currentTestClass,testMethodName,DEFAULT_QUALITY);  
  39.             }  
  40.             if(holo != null){  
  41.                 holo = null;  
  42.             }  
  43.               
  44.             break;                
  45.         } catch (Throwable e) {               

TAG: Android android robotium spoon 自动化测试

 

评分:0

我来说两句

Open Toolbar