书名:Android 应用测试指南
ISBN:978-7-115-41708-4
本书由人民邮电出版社发行数字版。版权所有,侵权必究。
您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。
我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。
如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。
• 著 [阿根廷]Diego Torres Milano
译 李 江
审 校 51Testing软件测试网
责任编辑 张 涛
• 人民邮电出版社出版发行 北京市丰台区成寿寺路11号
邮编 100164 电子邮件 315@ptpress.com.cn
• 读者服务热线:(010)81055410
反盗版热线:(010)81055315
《Android应用测试指南》是一本移动测试实用工具书。本书针对当前流行的技术、框架和工程质量改进工具进行了介绍,一步一步清晰地指导大家如何去写应用程序的测试用例,利用各种测试手段来保证Android项目质量。
本书首先介绍了TTD(Android测试驱动开发)。TTD是软件开发过程中一个敏捷模式,能让你在早期发现应用中的Bug。书中给出了一些典型的样例工程来示范测试,包括最简单的单元测试和最复杂的性能测试。另外,本书以诊断的方式来详细描述Android测试中较广泛、较流行的应用技术。对于梦想在Android测试领域启航的程序员和测试人员来说这无疑是一本非常珍贵、有用的参考书。
本书适合测试人员、测试开发人员、测试经理、移动开发人员阅读,也适合大专院校相关专业师生的学习用书和培训学校的教材。
Copyright ©2016 Packt Publishing. First published in the English language under the title Android Application Testing Guide.
All rights reserved.
本书由英国Packt Publishing公司授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式或任何手段复制和传播。
版权所有,侵权必究。
王 威(具有多年软件开发经验和软件测试工作经验,对产品测试的方法和管理流程有深刻认识,51Testing软件测试培训高级讲师)
王 琰(具有丰富的通信终端产品的测试及管理工作经验,51Testing软件测试培训高级讲师)
朴春龙(Mercury认证CPC,自动化测试专家,51Testing软件测试培训高级讲师)
周 峰(信息产业部认证系统分析员,51Testing软件测试培训高级讲师)
周春江(具有多年通信协议和通信终端设备的测试工作经验,51Testing软件测试培训高级讲师)
商 莉(多年从事软件开发、软件测试及质量保证方面的管理工作,51Testing软件测试培训高级讲师)
宋光照(具有深厚的开源软件测试实践经验,擅长嵌入式测试,51Testing软件测试培训高级讲师)
许爱国(获得项目管理PMP认证及软件质量专业技术职业资格认证,CSTQB注册讲师)
陆怡颐(具有多年IT研发领域工作经验,擅长架构设计,在多家公司实施敏捷开发的推广工作,有丰富的敏捷开发实践经验,51Testing软件测试培训高级讲师)
其他成员:张晓晓 严代丽
2007年年底,自从Android平台问世以来,Diego Torres Milano便开始研究Android平台技术、探索Android平台发展的可行性,主攻交互测试、单元测试、验收测试以及测试驱动开发的模式等领域。
在研究期间,他在个人博客上发表了大量相关的文章,同时还出席一些会议,以及担任讲师,如2008年阿姆斯特丹的移动开发者训练营,2009年日本东京的Linux研讨会,2009年Droidcon London会议(欧洲最大的Android开发者大会)等。他被欧洲很多大学邀请去授课,讲授Android训练课程等。
在此之前,Diego Torres Milano是多个开源项目的创始人兼开发人,主要作品有:CULT Universal Linux Thin项目以及非常成功的PXES Universal Linux Thin客户端项目。PXES项目后来被2X软件使用。它是基于Linux操作系统,特意为全世界成百上千的Thin客户研发的。2005年这个项目拥有的最高点击率是3500万,最高下载量是40万。这个项目在两方面产生了重大影响:一方面,由于安全和效率有很大改进,欧洲大公司决定改用PXES;另一方面,南美、非洲、亚洲一些机构、学院、学校决定使用PXES,因为PXES需要的硬件资源少,它对计算机供给、回收利用的意义非凡。
其他的开源项目还有:Autoglade(自动化生成GUI程序的工具)、Gnome-tla(其目标是基于自由软件,为UNIX及类UNIX系统构造一个功能完善、操作简单以及界面友好的桌面环境。它是GNU计划的正式桌面)、JGlade(Glade是GTK+图形用户界面产生器)。另外,他还在Linux各种版本发布上投入了很多精力,如RedHat(小红帽)、Fedora、Ubuntu。
Diego Torres Milano出席过很多技术大会,如Linux World(国际大型专业技术会议)、LinuxTag(欧洲最大的Free Software盛会)、guadec es(欧洲年度GNOME用户和开发人员会议)等。
他从事开源软件开发、为国际公司提供咨询服务已超过15年。
Paul Bourdeaux,他是一名资深软件工程师,Sundog的开发团队主管。他有强大的传统软件工程师背景,曾发表多篇关于移动市场营销和软件工程的论文。Paul在Sundog里是移动市场营销专家,而且他个人在移动软件工程和云计算工程上有很大的热情。
Tomas Malmsten,从事软件开发已有10多年。在此期间,他的工作内容涉及很多技术领域和商业领域。他专注于Java体系,他工作的所有项目,从大型企业系统到移动应用的开发,都是Java体系。
Tomas 是一个充满激情的软件工程师。他在做项目时,在各个方面都追求极致,从客户服务和交流到最后交付的工程,都一丝不苟,追求完美。
Gabor Paller 在1992年获得硕士学位,1996年获得博士学位。Paller博士在1998年进入Nokia公司工作,在研发中心担任开发工程师。他对无线协议开发、移动设备管理、移动Java和中间件非常感兴趣,也参与了标准化和联合研究项目活动。在离开Nokia之后,他在安里拉公司研究固定移动融合技术。2010年,加入爱立信研究感应电动机。Gabor Paller创办了一个非常受欢迎的博客叫“我的Android生涯”,评审过很多Android的书籍。
Abhinav Tyagi是一个从Pune信息技术学院毕业的学生,具有CDAC研究生学历,主攻先进计算机。他在孟买Antarix网络担任软件工程师期间,开发过几个Android应用。现在他为Nokia工作,职位是电信协议研发工程师。
首先,我想感谢我的家人:Laura、Augusto 以及 Octavio,感谢他们对我工作的体谅和对我的耐心。为了完成工作目标,我牺牲了很多陪伴家人的时间。
其次,我非常感谢我的朋友Caludio Palonsky,他是IN3集成信息系统的合作奠基人。我和他一起开创了众多Linux服务版本,为南美公司提供服务支持。我们一起体验这个长达15年之久的精彩旅程。他经常教育我要表现得像一位咨询师,少一点黑客气质(但是我做不到,嘿嘿)。另外,特别感谢Riscton的Peter Delia,因为他的点拨,我开始在2008年,在全欧洲提供Android培训。那个时候,大家都希望在移动领域能够有一个主流的开源平台,而Android实现了这个市场需求,让大家的美梦成真了。
最后,我想感谢所有帮我评审的工作人员以及Packt出版社的工作人员。他们给了我很多意见及建议,修正了早期初稿的不足,在他们的帮助下,才有了今天的成果。
众所周知,无论架构师在Android程序架构设计阶段花多少时间,不管程序员们在编程的时候有多仔细,总是不可避免犯错误,留下Bug。本书会指出那些常见并且容易规避的错误,尽可能帮助大家少犯一些常见的错误,从而提高您的开发效率和质量。本书汇集了作者大量的移动开发领域的项目经验、丰富的移动测试知识和实战经验。本书的主要内容如下:
第1章 测试入门。本章介绍不同类型的测试方法,以及这些测试方法在普通的软件工程和Android项目工程中的不同用法。
第2章 基于Android项目的测试。本章涵盖了如何在Android平台上进行测试、单元测试,Junit在Android项目测试中的使用方法、如何新建测试用例工程、如何执行Android的自动化测试用例。
第3章 本章更加深入地探索如何在SDK中建立测试用例,包括:使用断言,使用TouchUtil工具,测试接口,Mock对象,使用模拟器,以及测试用例类的层次UML图。
第4章 TDD,测试驱动开发。本章介绍测试驱动开发的原则。从介绍普遍的TDD原则、概念到讲述和Android平台密切相关的技术,这章会给大家展示很多源代码实例。
第5章 Android测试环境。本章介绍如何在不同的环境条件下执行测试用例。从创建Android虚拟机开始,通过设置不同条件的配置来变换测试用例执行的环境。最后,介绍了如何使用monkey来模拟用户操作。
第6章 BDD,行为驱动开发。本章介绍了行为驱动开发的相关概念。比如:最常用来表达测试的单词以及软件开发工程中的一些商业活动行为。
第7章 测试方案。本章列举了前面几章谈及的方法技术可以应用的常见环境场合,并以“食谱”的形式展示给大家。采用“食谱”的形式主要是方便读者选择性地学习、使用。“食谱”内容包括:Android单元测试、行为、应用、数据库、ContentProvider基类、本地和远程服务、UI(界面测试)、异常、分析器、内存泄漏。
第8章 持续集成。本章介绍了软件工程中的敏捷模式。软件在迭代中持续不断地变化,需要通过集成和不断地测试来保证质量。持续集成的方式能够减少花费的时间、改进质量、提高效率。
第9章 性能测试。本章节介绍了一系列与Android性能测试相关的基准配置和报表配置,以及Caliper的使用方法。
第10章 可选的测试策略。本章为读者提供了一些可选择的测试方案,包括:由源码编译Android程序、Emma测试覆盖率、Robotium自动化、基于host测试以及Robolectric。
首先,您要有一定的Android项目开发经验,因为本书不会对Android开发的基础知识进行讲述。学习本书的读者应该具备Android应用程序开发经验,或者至少已经熟悉Android开发知识。如果读者跟随本书中的测试样例,从几个API的Demo开始,然后逐步进入到更复杂、更深入的相关话题中,这样对您的帮助会更大。
为了能够用上各个章节的样例,读者需要做一些准备工作:安装一些软件、工具以及各章描述的特殊插件。所有样例都是基于以下版本:
如果你是一名Android开发者,正打算给你的应用做测试或者想优化你的开发过程,那么这本书正适合您,而且您无需测试经验。
在本书中,你会发现不同的信息采用了不同样式的文本。这里列出了本书的样式并作出了解释。
代码片段一般会这样写:“我们一般采用adb shell命令来唤起am命令。”
一段代码样式设置如下:
@VeryImportantTest
Public void testOtherStuff(){
Fail("Not implemented yet");
}
对于代码中需要引起读者特别注意、重点强调的内容,本书在文中会加粗表示。
public class MyFirstProjectTests extends TestCase{
public MyFirstProjectTests{
this("MyFirstProjectTests");
}
}
命令行的输入样式如下:
$ adb shell am instrument –w –e class com.example.aatg.myfirstproject.
test.MyFirstProjectTests com.example.aatg.myfirstproject.test/android
.test.InstrumentationTestRunner
所有的命令行输出样式如下:
08-10 00:26:11.820:ERROR/AndriodRuntime(510):FATAL EXCEPTION:main
08-10:00:26:11.820:ERROR/AndriodRuntime(510):java.lang.IllegalAccessError:Class ref in
pre-verified class resolved to unexpected implementation.
新名词或者重要的语句会加粗。屏幕上看到的单词、菜单栏、对话框中看到的单词通常会以这样的样式展示,比如:“选择测试的项目,然后单击Run As|Run Configurations”。
重要的笔记或者提醒将会以这种方式展示在框里。
提示将会用这种方式展示在框里
欢迎广大读者反馈您对本书的意见,您的反馈将有利于我们以后完善本书的内容。
编辑联系邮箱:zhangtao@ptpress.com.cn。
作者
本章介绍了不同类型的测试方法,它们在软件开发项目工程中的基本用法以及在Android项目中特殊用法。
关于“Android”和“开放手机联盟”,很多书中都有谈及,我们就不累述。本书涉及更高级的主题,我们希望在您阅读本书之前,最好有Android程序开发经验。不过,我们会先回顾一下测试的基本概念、技术、框架以及Android平台上的测试工具。
Android平台是在2007年末引进的。那时候,基于Android平台测试的技术很少,而且我们中一些人习惯于边开发边测试,将测试作为开发流程中紧密耦合的一部分,因此,是时候开发一些框架和工具来支持这种测试方式了。
那时候,Android平台用JUnit提供了一些不够成熟的功能支持单元测试,但是,支持力度不够并且帮助文档很少。
在我编写自己的库和工具的过程中,发现了Phil Smith的Positron库。他的库是开源的,非常适用于Android测试。于是,我在他的杰作基础上进行补充,新增功能、弥补不足。他们的库中不包含某些自动化测试的东西,所以,我新起了一个项目与之互补,项目命名为Electron。Positron和Electron两个项目,当然不是像真正的正反粒子那样互斥,相反,它们像正反粒子那样蕴含着大能量,能产生大量的光波。
后来,2008年初,Electron项目参加第一届Android开发挑战赛。虽然在一些类目中,Electron的分数不错,但是在框架类项目比赛中毫无立足之地。那个时候,Eclipse上已经可以执行单元测试了。但是,并不是在真机上执行测试,而是在本地开发机上的JVM虚拟机上。
Google也提供了执行应用程序的模拟器代码,通过Instrumention类实现了这一功能。当你打开模拟器运行程序时,Instrumention类会在你应用程序之前初始化,可以通过Instrument来模拟各种系统交互,执行程序。我们通过AndriodManifest.xml文件来设置模拟器。
在Android发展演变早期,我开始在博客里写一些文章来弥补这块测试的空白。本书就是将这些工作的演变和完成过程,用一种有序、容易理解的方式写下来,让你接触那些Android测试中的问题。
无论你多努力,代码设计花费多少时间,编程时候有多小心,你的程序中都会有Bug,这是不可避免的。
Bug和软件开发是息息相关的。硬件工程中,用Bug这个单词来描述瑕疵、问题、错误已经有几十年了,甚至比计算机出现得还早。尽管如此,关于Bug这个单词的故事是由哈佛大学的Mark II计算机操作员创造的,1878年,在爱迪生给蒂瓦达的信中可以看到这个单词的早期应用。
“我所有的发明都如此。第一步是直觉,随之是头脑风暴,然后困难都浮现出来。这些困难一点点被解决,然后Bug出现了,这些Bug就是所谓的小错误和困难。Bug出现后,需要投入几个月的精力去密切观察、学习,最终达到商业上的成功,否则,必然失败。”
众所周知,Bug会从很多方面对你的项目工程造成影响,越早发现并修复越好。无论你是为了优化用户体验,开发一个简单的Android程序;还是为设备操作员新建一个Android客户版本,Bug都在延迟你的交付时间,燃烧你的金钱。
在所有的软件研发模式中,“测试驱动开发”是软件开发流程中最敏捷的方式。它驱使你在开发过程中更早地发现、面对Bug,你也很可能预先解决更多的问题。
此外,比起那些在最后才进行测试的团队,利用这种测试驱动开发模式的研发团队,生产效率更高。如果你在移动行业参与软件开发,有理由相信赶时间的情况下,“测试驱动开发”这种方案不合适。因为,通常这种方式解决的问题都很可能是已经规避了的,这点很有趣。
2002年美国国家研究所的一项研究调查表明,每年软件Bug造成的损失595亿美元,如果软件测试执行得更好的话,超过三分之一的损失是可以避免的。
然而,请别误解以上所说的。软件开发没有特效药,是什么让你高效,让你的项目易于管理?是你有条理地利用这些方法和技术,掌控你的项目。
大家都清楚早期发现Bug会节约一大笔项目资源、减少软件维护费用。这就是为开发项目写测试用例的最好理由,不久你就会发现效率提高了。
另外,写测试用例的过程中,迫使你对需求了解更透彻,对要解决的问题了解更深入全面。如果你不了解被测对象,是不可能写好测试用例的。同样,写好测试用例可以清晰了解旧程序和第三方代码,让你能力倍增,更加有信心升级(旧程序、第三方代码)代码。
测试覆盖率越高,发现隐藏Bug的概率就越高。通过覆盖率分析,发现测试用例没有覆盖到的地方,就应该新增测试用例。
测试覆盖技术需要Android平台构造一个特殊的监控器来收集监测数据,但是不能发布,因为它会影响性能,从而严重影响应用的表现。
为了弥补这个空白,请访问EMMA。它是一个开源工具,用于测量和报告Java代码测试覆盖率,可以衡量类的覆盖率。它的报告有几个维度:
覆盖率报告同样可以设置成不同的格式。从某种程度上说,EMMA是基于Andoid框架的,所以,可以建一个带EMMA模拟器版本的Android系统。
我们将在第10章分析EMMA在Android系统上的用法,带大家完成一次完整的覆盖率测试,采用非传统的测试策略。
图1.1展示了在装了兼容的plugin后,经过EMMA覆盖率分析后,用Eclipse编辑器打开并分析的文件,绿色(运行后可看到,全书同)的代码行表示该行已经覆盖到了。
图1.1 EMMA覆盖率分析结果
不幸的是,这个插件不支持Android测试,因此,你只能用它来做Junit单元测试,而且你只能通过HTML来看Android覆盖率分析报告。
测试应该有自动化,每当你更改或者新增代码时,你就可以运行一部分或者全量测试用例来确保之前的逻辑是对的,以及新代码逻辑也是符合测试预期的。这就是我们说的持续集成,第8章我们将介绍。它依赖于自动化测试和自动化构建过程。
如果你没用到自动化测试,那实际上不可能把持续集成作为开发过程的一部分,而且很难保证代码变更情况下,不破坏现有代码逻辑。
一个Android应用,测试点是什么呢?严格地说,代码的每一行都应该经过测试。不过,根据不同的测试标准,你可以采用只需要覆盖到重要的路径分支或者一部分重要内容的方法。通常,有一些不可能被打破而产生Bug的地方就没有必要测试。比如:getter方法和setter方法测起来就没啥意思。这就好比编译器早就有自己的测试工程,而你也不可能在自己的代码中来测试编译器一样。
除了程序功能属于测试要点之外,Android应用还有一些特殊的地方需要考虑。我们将在下面几节进行描述。
Activity对生命周期中的事件是否能够正确响应是一个测试点。
比如,你的Activity在OnPause()事件和OnDestroy()事件中需要保存自身的状态,然后在OnCreate(Bandle savedInstanceState)时恢复状态。那么,你应该重复执行并验证在这些条件下状态是否能够正确保存、是否能恢复正常。
在测Activity时,还需要验证配置项变化事件的响应。因为配置变化后,当前的Activity需要重新生成。此时,你要验证新生成的Activity对事件的响应是否正常以及先前的状态是否恢复正常。定时循环事件也会触发配置的变化。因此,你还要看看应用是否能够正确处理这些事件。
数据库操作和文件的正确读写也是一个测试点。对这些操作的测试应该在底层系统测试中完成,在ContentProviders中进行高层次的测试,需要跟应用本身隔离开。
要对这些部件进行独立的测试,Android提供了Mock对象的工具,在andriod.test.mock包中。
在你发布应用程序之前,要确保它能在不同的设备上正常运行。至少你能把握大概的情况并想好对策。
在设备的物理特性中,有以下几个测试点:
由于有上述几个方面需要测试,虚拟器尤为重要。因为虚拟器可以让你通过简单配置达到上述条件的硬件特性。但是,如之前提到的,到最后的测试阶段,还是要用真机来操作,模拟真实用户的使用过程来测试。
在开发过程中,任何时间段都可以参与测试,这取决于采用何种测试方案。但是,我们推荐测试工作在项目开发早期就介入,甚至可以在完整需求出来之后、刚开始开发的时候就开始做准备。
基于被测对象的不同,有好几种不同的测试方法。但是无论采用哪种测试方法,测试用例都包含执行条件和执行结果,执行结果返回True或者False来表示用例是否正确。
单元测试,指的是程序员在开发阶段写的测试用例。这种测试用例需要将被测对象独立隔离起来,也就是Mock掉外部关联对象。单元测试用例应用是可以重复执行的。这也是为什么我们常把单元测试和Mock对象关联在一起。因为你要通过Mock对象来模拟外部交互从而达到隔离被测对象的目的。当然,这样的用例可以重复执行任何次数。例如,假设你要从数据库中删除某些数据,但是下一次执行这个用例时这些数据还需要用,因此,不希望这些数据真正被删除,这时候Mock数据库的返回,假装数据已经删除成功了。
JUnit是约定俗成的标准单元测试框架。它是一个简单、开源的自动化单元测试框架,由ErichGamma和KentBeck两位作者创建。
Android要用JUnit 3。这个版本没有注释,而是通过内部自查来感知测试用例的。一个典型的JUnit测试用例写法如框1.1中所示的代码,其中测试方法用高亮度显示。
框1.1 JUnit测试代码样例
/**
* Android Application Testing Guide
*/
package com.example.aatg.test;
import JUnit.framework.TestCase;
/**
* @author diego
*/
public class MyUnitTests extends TestCase {
private int mFixture;
/**
* @param name test name
*/
public MyUnitTests(String name) {
super(name);
}
/* (non-Javadoc)
* @see JUnit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
mFixture = 1234;
}
/* (non-Javadoc)
* @see JUnit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception {
super.tearDown();
}
/**
* Preconditions
*/
public void testPreconditions() {
}
/**
* Test method
*/
public void testSomething() {
fail("Not implemented yet");
}
}
如果你是从其他地方购买的书,可以访问http://www.PacktPub.com/support来注册用户,然后我们将源代码文件直接发E-mail给您。
我们将在下一节详细阐述测试用例的每个细节。
测试套件是个为人熟知的名词,它表示执行用例的标准流程模式。每个测试用例都用同一套标准流程。因此,它也是测试用例设计的基础。
通常情况下,按照Android的约定,它由一系列成员变量构成。通常以m开头,如: mActivity。但是,它也有一些扩展数据,作为数据库和文件系统操作的特殊入口。
这个方法是用来初始化测试套件用的。
通过重载这个方法,你可以新建对象,初始化元素。在每个测试用例执行之前,这个SetUp方法都会执行一次。
tearDown方法是在测试套件中最后执行的函数。
在测试用例执行过程中,会初始化一些对象,这些对象可以在tearDown函数中进行销毁。因为tearDown函数是每个测试用例最后必须执行的,是销毁对象的最佳阶段。
比如:你可以在tearDown中释放掉数据库连接以及网络连接。
JUnit设计的流程是这样的:首先,将整个库的用例都编译完;然后,在第二阶段再执行测试用例。因此,在测试执行过程中,执行器对所有用例都有强依赖。也就是说,对于那些用例很多、耗时很长的用例来说,在所有用例完成之前,是不会对变量、对象进行回收的。这点在Android测试中特别重要,因为在某些设备上测试失败的原因不是因为固有的逻辑问题,而是因为用例执行太多导致资源不足了。
因此,在Android应用中,你若测试使用了额外的、有限的资源,比如Services服务或者ContentProvides,那么,你应该注意要及时释放掉。在tearDown方法中,严格遵守将对象设置成null的规则,以便及时回收,避免一直占用资源,一直到所有用例运行完才释放。
通常,前置条件没法按照正常流程来测,因为常规的测试用例次序是随机的。因此,通常会写一个testPrecondition用例来专门测前置条件。虽然没法保证测试用例以一个特别指定的顺序进行,但是,将所有测前置条件的用例放在指定的地方是一个好的习惯,可以提高用例的可读性。
所有公有类型、返回值为void并且以test开头的函数都会被当作测试用例。JUnit 3不采用标注来标识测试用例,而采用函数名,这点与JUnit 4相反。在Android测试框架中,标注有@SmallTest、@MediumTest、@LargeTest,但是这些注释不会将一个函数标识为测试函数,他们是将测试用例分组的注释。通过分组,你可以选择性地执行某个组下面的所有用例。
实际测试的第一条规则,用描述性名字来命名测试用例,或者以测试条件命名。
比如testValues()、testConversionError()、testConversionToString(),这种命名方式是靠谱的。
测试时,要注意不仅仅是走正常的用例,异常和错误的测试用例也要覆盖到。在特定条件下执行用例后,要注意检查周边影响以及函数返回值是否跟预期一致。JUnit提供了一系列的断言assert*函数,它们将预期和真实结果作对比,不一致的时候抛出异常。这样,测试执行器将会处理这些异常,并在最终的结果中展现出来。
断言方法有很多,可以接受不同类型的参数,包括:
除了Junit提供的以上断言方法,Andriod扩展了两个特别的断言类:
Mock对象是指不调用真实的对象,而是调用模拟对象,获得指定结果,以达到将测试单元隔离的目的。
通常,使用这种方法是为了保证被测对象能够正常调用,并且,就像上面提到的,将被测对象跟周边环境隔离开。这样一来,测试用例就可以不受外部影响了,可以独立执行,并且可以重复执行。
Android测试框架支持Mock多个对象,这点对编写测试用例十分有用。当然,这些编译测试用例需要依赖一些东西。
Andriod测试框架提供的几个Mock类如下:
几乎平台上所有与你交互的部件都可以由以上几个类来生成。但是,它们并非真正执行,而是在每个方法产生UnsupportedOperationException的地方打桩,这样一来,你就可以创建你真正的Mock的对象,实现Mock的内容了。
最后,你在测UI部件的时候,需要考虑一些特殊因素。众所周知,在Android应用中只有主线程才允许更改界面交互。只有带有@UIThreadTest标记的函数才会在主线程执行,因此用来测界面的用例需要这个标记。另一方面,如果你想在UI线程中执行一部分测试用例,可以使用Activity.runOnUiThread方法,这个方法提供了可执行的测试操作。
TouchUtils是一个帮助类,提供了UI测试的常用帮助,指引你调用一般的事件方法,将事件传递到界面上,比如:
通过上述方法,你可以在测试用例中真实地远程控制您的应用程序。
Eclipse完全支持JUnit,而AndroidADT插件方便你测试Android工程。更有甚者,你无需使用IDE也可以执行测试用例和分析测试结果。
Eclipse也有个优势:在Eclipse里执行测试用例,执行不正确的时候,可以通过debug的方式,同时调试用例和代码。
在图1.2所示中,我们可以看到Eclipse执行了18个测试用例,花了20.008秒的时间,没发现问题,0失败。测试用例的名称以及执行过程都有展示。如果有一个失败了,错误跟踪会显示相关的信息。
图1.2 JUnit执行结果在Eclipse中的展示
其他IDE,像ItelliJ和Netbeans虽然集成Android开发的插件,但是并没有官方支持。
如果你现在没有在IDE中开发,你可以用ant来执行测试用例(如果你不熟悉这个ant工具请访问http://ant.apache.org)。通过Android命令行,用create test-project命令来启动,这个命令的帮助文字如框1.2所示。
框1.2 命令帮助信息
$ android --help create test-project
Usage:
android [global options] create test-project [action options]
Global options:
-v --verbose Verbose mode: errors, warnings and informational messages
are printed.
-h --help Help on a specific command.
-s --silent Silent mode: only errors are printed out.
Action "create test-project":
Creates a new Android project for a test package.
Options:
-p --path The new project's directory [required]
-m --main Path to directory of the app under test, relative to the
test project directory [required]
-n --name Project name
集成测试目的是用来测试模块与模块之间的交互情况。一般情况下,模块先会经过独立的单元测试,然后再组装在一起集成测试。
通常,Android应用上的活动需要跟操作系统进行交互。它们通过ActivityManager来控制活动的生命周期,访问资源、文件系统和数据库。
其他组件,譬如Services(服务组件)和ContentProviders(共享数据)也是同样的原理。它们也需要跟系统的其他部分进行交互来完成相应的功能。
在所有的测试用例中,Android测试框架提供了特殊的测试用例,以便测试上述组件。
在敏捷开发项目中,功能测试或者说验收测试一般是由业务方和测试人员来编写。这些用例通常是以业务场景的表达方式来给大家展现的。这里会有高层次的测试用例,用来测试一个用户需求或者特性的正确性和完整性。虽然这些测试用例是由客户、业务分析员、测试人员和开发人员协商制定的理想结果,但客户(产品的拥有者)是这些测试用例的首要负责人。
在这方面,有一些框架和工具可以帮助你,最著名的FitNesse,如图1.3所示,一句话,他们让你在Android开发阶段,轻松集成测试。另外还可以创建测试用例并且验证结果。
图1.3 FitNess集成测试结果图
最近,“行为驱动开发”BDD的新型模式风靡起来,简单说来,可以理解成“测试驱动开发”TDD的革新。“行为驱动开发”的目的在于在业务和技术人员之间建立起统一的术语以便增加相互之间的沟通理解。
“行为驱动开发”可以理解为基于活动的框架,有以下3个原则:
有了以上几个原则,业务人员通常都会参与测试用例的场景设计,并站在业务的角度,利用一些工具实现,比如jbehave。在下面的例子中,测试用例用类似于编程语言的方式表达出场景。
我们举个非常简单的例子来描述将场景转换成代码的方法。
场景如框1.3中所述。
框1.3 场景描述
假设我有一个温度转换器,
当我输入100摄氏度时,
我得到的结果是212华氏温度。
这个场景翻译成代码如框1.4中所示。
框1.4 对应框1.3翻译出来的伪代码
@Given(“我有一个温度转换器”)
public void createTemperatureConverter(){
// do nothing
}
@When(“我将摄氏温度输入对应的文本框”)
public void setCelsius(int celsius){
mCelsius=Celsius;
}
@Then(“我将在华氏温度的字段看到相应的华氏温度”)
public void testCelsiusToFahrenheir(int fahrenheit){
assertEquals(Fahrenheit,
TemperatureConverter.celsiusToFahrenheit
(mCelsius));
}
性能测试,是用重复的方式来探测部件中某些特点的性能。如果说,应用的某些地方对性能提升有要求,那么最好的衡量优化效果的办法,就是拿优化之前和优化之后给出的性能报告做对比。
众所周知,一次考虑不周到的优化带来的后果总是弊大于利的,因此,为了避免优化后不仅没达到效果,反而把之前的功能影响到了,最好对应用的整体性能有个清晰的了解,明确升级之后对应用的影响。
Android 2.2里面的Dalvik JIT编译器改变了一些Android开发中常使用的优化方案。迄今,在Android开发中,每一个性能优化升级都推荐有性能测试保证。
系统测试是将部件组合起来作为一个整体来测试,检查部件之间的交互情况,软件和硬件都会涉及。通常,系统测试包括以下额外的测试类,比如:
Android提供了一个高级的测试框架,这个框架是JUnit的一个扩展,在标准JUnit的基础上插入了方便执行上述测试的插件。有的情况下,我们需要再装一些工具,而且集成这些工具大多情况下都很简单和直接。
Android测试环境的关键特性包括以下这些:
模拟器框架是测试框架的基础。模拟器控制被测的应用,并且允许插入桩来模拟应用的某些部件的执行。比如,你可以在应用启动之前创建模拟的Context,应用程序将会用模拟的Context来执行。
所有的应用程序跟周边环境的交互都可以通过上述方式来控制。你可以将应用程序封闭到一个十分严谨单一的条件下来得到预期的结果,强行设置某些方法的输出或者模拟ContentProvider中的常量、数据库、甚至文件系统的内容。
一个标准的Android工程都会有相应的测试工程,这个测试通常以Test开头。在Test工程中,AndriodManifest.xml中定义了使用的机器。
举个例子描述,假设你的工程配置文件如框1.5所示。
框1.5 主工程的AndriodManifest.xml配置文件
< ?xml version="1.0" encoding="utf-8"?>
< manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.aatg.sample"
android:versionCode="1"
android:versionName="1.0">
< application android:icon="@drawable/icon"
android:label="@string/app_name">
< activity android:name=".SampleActivity"
android:label="@string/app_name">
< intent-filter>
< action android:name="android.intent.action.MAIN" />
< category android:name=
"android.intent.category.LAUNCHER" />
< /intent-filter>
< /activity>
< /application>
< uses-sdk android:minSdkVersion="7" />
< /manifest>
在这个项目里,相关的测试工程配置文件AndriodManifest.xml如框1.6所示。
框1.6 测试工程的AndriodManifest.xml配置文件
< ?xml version="1.0" encoding="utf-8"?>
< manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.aatg.sample.test"
android:versionCode="1" android:versionName="1.0">
< application android:icon="@drawable/icon"
android:label="@string/app_name">
< uses-library android:name="android.test.runner" />
< /application>
< uses-sdk android:minSdkVersion="7" />
< instrumentation
android:targetPackage="com.example.aatg.sample"
android:name="android.test.InstrumentationTestRunner"
android:label="Sample Tests" />
< uses-permission android:name="
android.permission.INJECT_EVENTS" />
< /manifest>
这里的模拟器包作为主项目包,带着.test后缀。
定义模拟器的时候,会指定目标包和测试执行器,在这个情况下,默认的客户端执行器是android.test.InstrumentationTestRunner。
另外,被测应用和测试工程一样,都是apk安装的Android程序。它们都在同一个进程中,因此,能访问相同的功能特性。
当你执行一个测试应用程序的时候,行为管理器(http://developer.andriod.com/intl/de/reference/andriod/app/ActivityManager.html)利用模拟器框架来启动和控制测试执行器,然后测试执行器反过来利用模拟器工具来关闭主程序运行的实例,启动测试进程,最后,在同一个进程中启动主程序。这种方式使得各种各样的测试应用可以直接在主应用中工作。
在项目开发过程中,你的测试用例必须在不同的设备上执行。从操作简单、方便,到响应速度等方面考虑,都要求最后必须在具体设备上测试,并且是在所有类型的设备上测试。
当然,有的测试用例会在本地JVM虚拟机上执行,有的用例在开发机上执行,有的在Dalvik或者活动虚拟机上执行,具体情况取决于测试用例的特点。
上述执行用例的方式都有各自的优缺点,幸运的是,你可以自由决定如何来执行你的用例。
仿真器是一个非常棒的执行平台,可能是最强大的,因为它可以让你修改测试过程中所有的参数、配置以及各种执行环境。测试最根本的目的是让你的程序能够正确处理所有场景,因此,最好在程序发布之前发现所有的问题。
性能测试需要使用真机,因为模拟仿真设备多少跟真机会有不同的地方。只有用真机才能体会到用户的真实感受。渲染、滚屏、投掷以及其他场景都需要发布之前用真机测试一次。
我们复习了软件测试中的主要概念以及Android测试中的特殊点。这些知识为我们开启探索软件测试的优点提供了必备条件。
本章主要讲述了以下内容。
我们还从Android测试的角度分析了这些技术,提到了用模拟器来执行测试用例的方法。下面几章,我们将开始用实例来分析上面提到的技术、框架、工具使用的细节。
既然我们已经了解了测试的基本概念及其重要性,那么现在是时候付诸于实践了。
在本章,我们将要讲述:
我们会创建一个简单的Android主程序和与之对应的测试工程项目。主工程基本上是一个空项目,因为我们将重点看测试部分。以我个人经验,建议没有任何Android测试经验的同学好好学习下本章。如果你有过Android工程的经验,并且用过相关的测试技术,那么你完全可以以浏览的方式读读本章,复习一下概念即可。
实践证明,测试最好在独立的、没有干扰的环境中执行,当然,这不是强制措施。Android的ADP插件支持这个功能,但也做不到完全隔离。前段时间,我曾经发表过一篇文章,文章描述了人工获得两个相关工程的方法——主工程和测试工程。文章中并没有数据证明隔离测试的优势,但是,我们可以发现。
前面一章我们已经对Junit进行了简单介绍,因此这里就不再累述。值得一提的是,Android测试项目的默认测试框架是Junit。Eclipse,AndroidADT插件以及Ant都支持Junit框架,所以你不用担心自己没有使用IDE开发而不能使用Junit。
你可以自由选择测试框架。
后面大多数的实例都是基于Eclipse的,因为Eclipse也是最常用的工具。那么,我们不废话了,打开Eclipse一起开始学习。
我们先创建一个新的Android项目。在Eclipse里面单击菜单栏File-new-Project -Andriod-Andriod Project。
然后,给组件命名如下,我们取个特殊点儿的名称,信息如表2.1所示。
表2.1 项目信息
项目名称:My First Project |
构建目标:Andriod 2.3.1 |
应用名称:My First Project |
包名:com.example.aatg.myfirstproject |
创建活动:MyFirstProjectActivity |
最小SDK版本:9 |
当你输入完这些值时,会出现下面的对话框,如图2.1所示。
图2.1 新建Andriod工程对话框
单击图2.1中Next按钮,会进入创建Android测试项目的对话框界面。注意这里将会有一些值是继承主项目的,设置值或者是跟主项目对应。
另外一种为Android项目新建测试工程的方法是:选中主项目,然后单击Android Tools | Create Test Project 。打开之后“测试目标”选中被测主项目,然后其他的值会自动设置好。
图2.2展示了创建一个Android测试项目需要输入的一些参数值。当所有的值都填写完毕后,我们就可以单击Finish完成。
图2.2 新建Android工程过程图
创建完前面提到的两个项目之后,我们打开包浏览器(Package explorer),就会看到像图2.3一样的界面。我们注意到这里存在两个相关的项目,每个项目都有自己独立的组件和项目属性。
图2.3 项目的包浏览视图
如果你已经准备好了这些基本工作,现在开始添加一些测试用例了。
当然,现在还没什么需要测试的。不过,既然我们已经有了“测试驱动开发原则”的知识基础,那么,我们就先添加虚拟测试用例,提前熟悉下相关技术吧。
测试用例最好添加在MyFirstProjectTest中的src文件夹中。当然,这不是强制性的,只是大家通常都这样放,算是实践得出的最佳位置吧。测试包最好跟被测包的目录结构保持一致,方便后续的用例维护。
现在,我们还没有专注在测试用例的编写上,还只是了解了一些概念和测试文件的摆放位置。
如上所述,我们将在Test项目中的src目录下面添加测试用例。
我们特意利用Junit测试来创建一个单元测试用例。Eclipse提供了操作向导来帮助大家(File | New | Junit Test Case)。
我们打算选择主工程下面的活动Activity作为被测对象,尽管这个例子跟Activity实际内容无关。
创建测试用例时,我们有以下的值需要设置,如表2.2所示。
表2.2 新建用例填写的表格
Junit版本:Junit3 |
源代码文件夹:MyFirstProjectTest/src |
包名:com.example.aatg.myfirstproject.test |
工程名:MyFirstProjectTests |
父类:junit.framework.TestCase |
你想要创建什么桩函数?setup(),teardown(),constructor() |
被测类:com.example.aatg.myfirstproject.MyFirstProjectActivity |
严格说来,我们这里面有setUp()、tearDown()和constructor()选项,不过这不影响我们这里的例子,因为它比较基础、简单。但是,在实际的场景中,这三个函数都是需要的。
输入完上面描述的值之后,我们的Junit测试用例创建界面如图2.4所示。
图2.4 新建Junit用例过程图
最基本的测试框架已经准备好了;剩下的工作就是添加一些虚拟的测试用例来确认流程是通畅的以及所有功能的执行都与预期一致。
Eclipse同样提供了插入桩的测试方法。单击Next会弹出下面的对话框,这里你可以选择需要插入桩的被测函数,如图2.5所示。
虽然说通过插入桩的方式十分方便测试,也很有用。但是你需要考虑,测试应该用行为来驱动而不是通过调用方法来驱动,毕竟行为驱动更加真实,更加符合实际。
图2.5 新建用例选择插入的方法函数
现在我们有了测试用例模板,下一步就是根据需求来完成测试用例。打开最新创建的用例,添加一个测试函数 testSomething()。最好的做法是,在类的最后添加用例。
写的用例代码如框2.1所示。
框2.1 testSometing源代码
/**
*
*/
package com.example.aatg.myfirstproject.test;
import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
/**
* @author diego
*
*/
public class MyFirstProjectTests extends TestCase {
public MyFirstProjectTests() {
this("MyFirstProjectTests");
}
/**
* @param name
*/
public MyFirstProjectTests(String name) {
super(name);
}
/* (non-Javadoc)
* @see junit.framework.TestCase#setUp()
*/
protected void setUp() throws Exception {
super.setUp();
}
/* (non-Javadoc)
* @see junit.framework.TestCase#tearDown()
*/
protected void tearDown() throws Exception {
super.tearDown();
}
@SmallTest
public void testSomething() {
fail("Not implemented yet");
}
}
这个测试用例会永远执行失败,错误展示如下:Not implemented yet。我们在测试用例中使用了junit.framework.Assert类的fail函数来使得这个测试用例失败,并且显示上述给出的错误信息。
通过命令行,用am instrumentation命令来执行具体的测试用例,需要一个不带入参的构造函数,这点在后面会解释到。
下面的表2.3中列出并描述了我们测试用例类中用到的特殊方法。
表2.3 测试用例中的特殊方法
方 法 |
描 述 |
|
---|---|---|
setup |
启动方法,设置初始化属性。比如,网络连接或者创建一个目标对象方便测试。这个方法在测试执行之前会被调用 在这个例子中,我们只是调用了父类中的方法 详情看第1章,测试入门 |
|
tearDown |
关闭某些属性。比如,关闭网络连接。这个方法在测试执行完毕后被调用 在这个例子中,我们只是调用了父类的方法 详情参考第1章,测试入门 |
|
testSomething |
测试方法样本。我们通过Junit3的反射机制,用test来标注测试函数,这样就可以被框架检测到并作为测试用例来执行了。 测试方法的函数名应该很清晰可以辨认出要测什么属性 |
仔细看看上面测试类中的函数定义,你会发现我们是用@MediumTest注释来标注测试函数的。这种注释用来给测试用例分组,然后可以单独执行同一个组的测试用例。
还有一些同样类型的其他注释,如表2.4所示。
表2.4 标注的含义
注 释 |
描 述 |
|
---|---|---|
@smallTest |
注释该测试用例为“小集合”测试用例,它将会在“小集合”用例一起执行 |
|
@MediumTest |
注释该测试用例为“中型集合”测试用例,它将会在“中型集合”用例一起执行 |
|
@LargeTest |
注释该测试用例为“大集合”测试用例,它将会在“大集合”用例一起执行 |
|
@smoke |
注释该测试用例为“冒烟”测试用例,它将会在“冒烟”用例一起执行。在andriod.test.suitebuilder.smokeTestSuiteBuilder编辑器中,所有的测试执行都会执行该注释下的测试用例 |
|
@FlakyTest |
在InstrumentationTestCase类的方法写上这个注释。一旦测试用例执行失败,这个注释下的函数将会重复执行。重复执行的最大次数可以设置,默认值是1。有时候随着时间的变化、环境的影响,一些测试用例执行会偶然失败,这时候这种方式就有用了 比如,假设你要将你的最大重复执行次数设置为4,那么你可以标注为: @FlakyTest(tolerance=4) |
|
@UIThreadTest |
在InstrumentationTestCase类中的测试方法下,测试方法将在主线程中执行,又叫UI线程 假设你想修改UI或者在同一个测试用例中利用一些技术进入设备,这时候设备方法可能调用不了。在这种情况下,你可以求助于Activity.runOnUIThread方法使得你可以创建Runnable并且在UI线程中运行你的用例
} |
|
@Suppress |
这个注释会把你的测试用例从一个测试集合中排出出去 这个注释可以在任何类中使用,即便是没有测试集合的类、或者只含有一个测试函数,或者只含一个测试集合的类,都可以将相应函数设置这个注释,排出在外 |
现在我们的测试用例也准备好了,下一步就是要执行测试用例了。
执行测试用例的方法有很多种,我们这里一个个地分析。
另外,我们在前面的章节中提到的注释,可以让测试用例按照组或者种类执行,这种方式要按实际需求来执行。
如果你采用了Elicpse作为开发环境,从Eclipse中执行测试用例可能是最简便的方式了。这种方式会执行包中所有的用例。
选择测试工程,然后单击 Run As -> Andriod Junit Test。
如果没有找到合适的设备或者模拟器,那么会自动启动一个。然后,测试用例开始执行,最后执行的结果会在Eclipse中的DDMS中展示出来。这个窗口需要手工打开,如图2.6所示。
图2.6 Eclipse中DDMS展示测试用例的执行结果
从Eclipse DDMS窗口的LogCat视图中,你可以看到更加详尽的执行过程信息和结果,如图2.7所示。
图2.7 DDMS中的logCat视图
你可以选择执行单个测试用例,在测试过程中经常会遇到这种需求。选择测试工程,然后单击 执行1——执行设置选项。
然后,创建一个新的测试配置,配置如表2.5所示。
表2.5 测试配置
执行单个测试用例 |
检 查 配 置 |
---|---|
项目名称 |
MyFirstProjectTest |
测试类 |
Com.example.aatg.myfirstproject.test.MyFirstProjectTests |
当你像往常一样执行测试用例的时候,只有这个用例会被执行。这种情况下,我们只执行了一个用例,执行后的结果跟之前发的截屏类似。
在Eclipse编辑器中,还有另一种快捷方式可以执行单个用例。选中你的函数名,然后按Shift+Alt+XT组合键,或者右键,然后选择Run as-Junit Test。
模拟器默认的系统里面安装了开发工具,提供一些手动工具和设置。在这些工具中,我们可以找到一个特别长的列表,如图2.8所示。
现在我们研究下设备,因为需要利用设备来执行测试用例。上述的应用列表展示了AndriodManifest.xml中instrumentation标签下定义的所有包。默认情况下,会展示默认设备的配置,也就是andriod.test.InstrumentationTestRunner里面的默认配置。在这个文件里,如果有两个以上的包配置,那么识别到底用哪套配置就成问题了。为了解决这个问题,你可以手工添加一个选项,在设备标签下,如图2.9所示。
图2.8 工具列表
一旦设置成功,设备配置列表将会重新展示,我们的包将会在新标签下面显示,在执行之前,选择好就可以了,如图2.10所示。
如果测试用例按照这种方式执行,那么你就可以通过LogCat来看执行结果。
图2.9 在设备标签下添加选项
图2.10 新标签
你可以看到,如果你不设置可选标签,那所有设备都会在默认的标签andriod.test.InstrumentationTestRunner下面展示出来,这点之前也提到过。
最后,我们还可以从命令行来执行测试用例。如果你想自动化或者用脚本来控制测试,命令行很有用。
我们用 am instrument 命令来执行测试用例(严格地说是 am 命令和instrument子命令),这个命令能够唤起设备、指定包名,还有其他功能选项。
你可能在想,am是什么?am是Activity Manager的缩写,它是Android系统内部的一个主要部件。在系统启动的时候,系统服务将会唤起这个行为管理器。行为管理器负责管理所有的行为以及行为的生命周期。另外,我们可以看到,它要负责行为设备的控制。
am instrument 命令行的一般用法如框2.2中所示。
框2.2 am instrument命令用法
am instrument [flags] < COMPONENT>
-r: print raw results (otherwise decode REPORT_KEY_STREAMRESULT)
-e < NAME> < VALUE>: set argument < NAME> to < VALUE>
-p < FILE>: write profiling data to < FILE>
-w: wait for instrumentation to finish before returning
下面的表2.6中总结了最常用的选项。
表2.6 am命令常用选项
选 项 |
描 述 |
---|---|
-r |
打印原始结果。用于手机最原始性能数据 |
-e<名字><值> |
通过名字设置参数,我们将简单示范下这个功能。这是一个可选的配置项,让我们设置<名称、值> |
-p<文件名> |
将需要存档的数据,保存到外部文件中 |
-w |
等待设备结束后退出。通常在命令行中使用,虽然不是一定要这样写,但是在手工执行时,如果不这样写,执行完用例后看不到测试结果 |
要触发am命令,我们使用adb shell命令或者你打开模拟器,你可以直接在shell命令窗口执行am命令。
除了性能测试用例,下面的命令行将会执行所有的测试用例,如框2.3所示。
框2.3 测试用例执行结果
diego@bruce:\~$ adb shell am instrument -w com.example.aatg.
myfirstproject.test/android.test.InstrumentationTestRunner
com.example.aatg.myfirstproject.test.MyFirstProjectTests:
Failure in testSomething:
junit.framework.AssertionFailedError: Not implemented yet
at com.example.aatg.myfirstproject.test.MyFirstProjectTests.testSomethi
ng(MyFirstProjectTests.java:22)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRu
nner.java:430)
at android.app.Instrumentation$InstrumentationThread.
run(Instrumentation.java:1447)
Test results for InstrumentationTestRunner=.F
Time: 0.2
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0
为了执行某个具体的测试文件中所有测试用例,你可以用下面的命令,如框2.4中所示。
框2.4 Shell命令:执行特殊用例文件的所有用例
diego@bruce:\~$ adb shell am instrument -w -e class com.example.aatg.
myfirstproject.test.MyFirstProjectTests com.example.aatg.myfirstproject.
test/android.test.InstrumentationTestRunner
另外,我们可以选择具体哪个测试用例要在命令行下执行,如框2.5中所示。
框2.5 Shell命令:执行单个用例**
diego@bruce:\~$ adb shell am instrument -w -e class com.example.aatg.
myfirstproject.test.MyFirstProjectTests\#testSomething com.example.aatg.
myfirstproject.test/android.test.InstrumentationTestRunner
这种方式只能执行不带参数的构造函数的测试文件,这就是我们需要在命令行上加测试方法名的原因。
我们前面提到了,测试用例可以被分为不同的种类,利用注释(测试注释)来标明它属于哪个种类。在执行命令的时候,可以通过下面的选项来指定执行某个种类下所有测试用例,选项详情如表2.7所示。
表2.7 用例执行方式选项
-e unit true | 执行所有单元测试用例。有的测试用例并不是在InstrumentationTestCase中(并且也不是性能测试用例) |
-e func true | 执行所有功能测试用例。所有的测试用例都是从InstrumentationTestCase中来的 |
-e perf true | 包括性能测试 |
-e size { small | medium | large } | 执行小型、中型或者大型测试用例,具体执行哪些,要看测试用例上标注的类型 |
-e annotation <标注名> | 执行所有带有这个“标注名”的测试用例。这个选项不包括尺寸选项 |
在我们举的例子中,将测试用例testSomething()标记为@SmallTest。这个测试用例就属于Small这类了。因此,在按照测试集合规模的大小来执行用例时,这个用例将和所有标记为SmallTest的用例一起执行。
框2.6所示的命令行将会执行所有带@SmallTest标记的测试用例。
框2.6 执行带SmallTest标记的命令
diego@bruce:\~$ adb shell am instrument -w -e size small com.example.
aatg.myfirstproject.test/android.test.InstrumentationTestRunner
为了满足客户有另外的需求,比如说,有更大的测试集合需要标记,现有的尺寸标识不了。大家可以创建一个自己的个性标签,然后按命令行的方式具体定义。
比如,我想把一些非常重要的测试用例放在一起,于是我们可以创建一个叫做@VeryImportantTest的标签,如框2.7中所示。
框2.7 新建VeryImportantTest标签
package com.example.aatg.myfirstproject.test;
/**
* Annotation for very important tests.
*
* @author diego
*
*/
public @interface VeryImportantTest {
}
接下来,我们将创建另一个测试用例,并标记为@VeryImportantTest,如框2.8所示。
框2.8 新建测试用例,打新标签**
@VeryImportantTest
public void testOtherStuff() {
fail("Not implemented yet");
}
因此,像我们上面提到的,我们可以在am instrument命令行中执行带有这个标签的测试用例如框2.9所示。
框2.9 am instrument命令执行新标签下的用例
diego@bruce:\~$ adb shell am instrument -w -e annotation VeryImportantTest \
com.example.aatg.myfirstproject.test/android.test.
InstrumentationTestRunner
我们将在第9章“性能测试”中复习一下性能测试的细节,这里我们会介绍下am instrument命令中的一些选择项。
你需要在命令行中添加下面的选项,才能将性能测试用例添加在执行中,如表2.8所示。
表2.8 性能测试用例命令选项
-e perf true | 包括性能测试。 |
有时候你只是想知道测试用例是否能够跑起来,而不是真正想做测试。这时候你可以将下面的选项加到你的命令行中,如表2.9中所示。
表2.9 显示测试用例命令选项
-e log true | 显示将要执行的测试用例,而不是真正执行。 |
这种方式在你写脚本或者编译的时候很有用。
当然,大家应该想到的是,你的测试用例代码也可能有问题。于是,通常都要调试用例,并且在LogCat中打印日志信息以便调试。还有一种更复杂的办法,就是启动调试工具来调试,有两种方式。
第一种方式更简单:利用Eclipse的便利性,不需要去记复杂的命令行选项。在最新版的AndroidADT插件中,有选项 Debug As | Andriod Junit Test。然后,你可以在测试用例中设置断点调试代码。
设置断点的方式就是在编译器中选中你要暂停的那行,然后利用菜单选项“执行|切换行”断点。这样你就可以轻松地将你测试代码切换成调试模式,等待调试器连接上就可以了。不用担心,这个特别简单。在你想要调试的测试用例中添加下面这小段代码。加在哪里并没有关系,因为调试器总是停留在你设置断点的地方。这种情况下,我们决定在构造器中添加Debug.waitForDebugger( ),如框2.10所示。
框2.10 测试用例中添加debug代码
public class MyFirstProjectTests extends TestCase {
private static final boolean DEBUG = true;
public MyFirstProjectTests(String name) {
super(name);
if ( DEBUG ) {
Debug.waitForDebugger();
}
}
当你像往常一样执行测试用例,单击 Run As | Andriod Junit Test,你可能看到这样一个窗口,要求你转换视图,如图2.11所示。
图2.11 视图转换提示框
一旦切换之后,你将会看到一个标准的调试界面和会话。另外,如果你不能或者不想改变你测试的代码,那么可以设置断点之后,在am instrument命令后加上下面的项目,如表2.10中描述。
表2.10 打开debug选项
-e debug true | 打开调试器 |
当你开始测试执行的时候,测试执行器会等待调试器连接上来。调试用例的命令行如框2.11所示。
框2.11 连接调试器命令
$ adb shell am instrument -w -e debug true com.example.aatg.
myfirstproject.test/android.test.InstrumentationTestRunner
等待调试进入到你断点这行时,你会看到下面这行,如框2.12所示。
框2.12 debug断点行时,看到的日志
com.example.aatg.myfirstproject.test.MyFirstProjectTests:
当调试器连接上之后,这行才会消失,调试会话框才会出来。
之前已经提到了,am instrument命令中接收输入<名称,值>的键值对,含义如表2.11中所示。
表2.11 am instrument命令的其他选项
名 称 |
值 |
---|---|
Package |
包名的全称或者测试应用中的多个包名 多个包名用逗号隔开 |
Class |
测试用例中待执行的测试用例类的全称 可以包含测试方法名称,方法和类名用#隔开 |
Coverage |
True 执行Emma覆盖率,执行结果会写到一个特殊指定的输出文件中 我们将在第10章 可选的测试策略中详细谈到在我们测试用例中添加Emma覆盖率的技术 |
我们复习了Android测试背后的主要技术和工具。
本章覆盖的内容如下。
下一章,我们将要分析前面提到的技术、框架和工具。将会更加详细地阐述它们的用途,并举例说明。