iOS测试指南(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-25 21:00:47

点击下载

作者:芈峮

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

iOS测试指南

iOS测试指南试读:

版权信息书名:iOS测试指南作者:芈峮排版:上官雅弘出版社:电子工业出版社出版时间:2014-05-01ISBN:9787121227585本书由电子工业出版社授权北京当当科文电子商务有限公司制作与发行。—·版权所有 侵权必究·—推荐序一 你的iOS App还在裸奔么?

2014年3月的TIOBE编程语言排行榜,Objective-C排行第三位,紧随C语言和Java之后,甚至在C++前面。其他这三种语言,都是有着非常广泛的应用场景和悠久的使用历史的。Objective-C的诞生也并不晚,但是仅在近年才开始流行,而且Objective-C几乎只用于iOS/Mac平台开发。所以,这一排行榜多少能告诉我们iOS开发到底有多热。2013年7月,苹果公司CEO Tim Cook宣布仅在中国就有50万iOS开发者。

然而,这个行业的开发水平和测试水平到底怎么样呢?我在新浪微博简单地做了一个调查,34.5%的人说他们完全没有任何测试;44.8%的团队有独立测试人员;而有单元测试、UI自动化测试、持续集成的团队就屈指可数了。

那么,在这个平台下测试不重要吗?我觉得恰恰相反。

任何App要想在苹果的AppStore上架,都需要经过苹果的审核员的审核,不管你是世界五百强的大公司,还是小作坊,都会一视同仁,绝无例外。如果你的App没有经过良好的测试,被审核员发现有闪退、崩溃或者其他严重质量问题,他们会毫不犹豫地拒绝你的App。而你则需要修改App,重新提交,这往往就意味着再等7~8天的排队才有机会被审核。

如果你的运气好,Bug没有被审核员发现,或者说,在审核员审核的环境下,你的App表现良好,你的App就成功上架了。但是如果它在用户的iPhone/iPad上面发生闪退、崩溃,等等,其实你会更倒霉。因为愤怒的用户会迅速让你收获大量的1星,即使你好不容易做了一年的好评度,也会一下子跌落谷底。如果你熟悉AppStore的话,就知道这往往意味着你的下载量将一落千丈,你的App也有可能从此无人问津。

App这个形式和网站类应用最大的区别就是,如果网站的程序员发现某个页面有一个小Bug,修改后,经过合理的内部审核流程,它的一个部署脚本就可以升级代码,用户在刷这个页面和那个页面之间的空隙,这个升级就完成了。而iOS App出现了问题以后,不管你修改得多快,都需要被苹果的审核员审核,这往往需要7~14天,然后,你往往需要在用户方便的时候,通过重新下载整个App的方式得到更新(虽然iOS 7.0后,更新普遍可以自动在后台完成,但是时间周期仍旧是这么长)。

所以,对iOS开发者强调测试的重要性,我觉得说100遍、1万遍都不嫌多,都有其现实意义。

但是为什么还有那么多团队和个人开发者没有进行完善的测试呢?

懒、侥幸心理、怕麻烦一定是少不了的。

还有,我觉得就是一般的入门书、教程,甚至包括苹果的官方文档,讲到的测试部分都太简单,缺乏可操作性。

所以,当我得知本书的作者芈峮在写这样一本专注于iOS平台测试工具和方法的书的时候,我很高兴,而他邀请我做序的时候,我感到十分荣幸。

最早知道芈峮时,他还在豆瓣的测试团队工作,他做了一个开源的测试工具ynm3k(要你命3000)。仅仅是这个充满幽默感的名字就征服了我。

后来我了解了一下这个工具,由此我才知道原来在iOS下也是可以进行UI自动化测试的。在此之前,我只是经常跟人们在一起人云亦云地说:“嗯,单元测试是好,但是iOS开发主要都是UI逻辑,这可怎么测试呢?”

不经过完善的测试,我们的App其实就是在裸奔,会不会出问题,会出什么样的问题,完全看运气。在以前,我们可以有这样或那样的借口,甚至可以直接说,我就是找不到资料嘛,学不会那还能怎么办呢?

现在,有了这本书,对不起,没有借口了,请把这本书带回家,仔细阅读,按照这本书改善你的开发测试流程,别再让你的App裸奔了……郝培强OurCoders.com创始人2014年3月11日于上海推荐序二

前几天芈峮告诉我,他完成了一本与iOS测试相关的书,希望我能写个推荐序。芈峮在豆瓣的测试团队时,就一直专注于移动App和移动Web应用的测试。经常能够见到他鼓捣各种工具,用充满创意的方式将小工具连接起来解决实际问题,当然,最让人称道的,是他对移动测试的热情。

读完了芈峮这本不算厚的书,不觉眼前一亮。这本书并没有刻意地为了拔高而选择一些生涩的主题,而是系统性地介绍了移动测试的方方面面:对于移动App的测试,本书介绍了如何搭建移动App测试的环境,如何进行移动App的单元测试,如何选择和使用UI自动化测试方案;对于移动Web应用的测试,本书介绍了如何使用Selenium和Appium轻松地完成这类应用的测试;此外,芈峮还格外体贴地在书中介绍了如何使用持续集成(CI)更好地搭建持续测试的环境——在Web开发中引入持续集成并不少见,但在移动应用开发中发挥持续集成的威力,这可是真有些挑战的!

随着移动互联网渐入佳境,越来越多的组织和个人开始进入移动互联网领域。在测试行业内,越来越多的测试者开始关心移动应用的测试。移动应用明显有不同于Web应用和桌面应用的特点,移动开发平台(iOS、Android,也许还可以算上WP)自身的特性,设备的兼容性(即使是iOS的开发者,现在也不得不考虑兼容性的问题了),移动设备本身的某些特性(网络连接,交互特性等)都给移动应用的测试带来了新的挑战。明显能够看到,移动应用的测试已经成为测试领域的热点,近期在各个渠道也颇能见到一些与移动应用测试相关的介绍文章。然而,大部分我读到的介绍性的文章都是对某工具的介绍,或是某个具体的测试手段,虽然热闹非凡,但对那些希望系统性地一窥移动应用测试门径的测试者来说,实在是不堪大用。从这一点上来说,芈峮的这本《iOS测试指南》算是恰逢其时。本书这些扎扎实实来自一线实践的内容,一定能够让对这方面有兴趣的工程师系统性地了解移动应用测试。

当然,移动应用的测试热才刚刚在测试领域内兴起,这本《iOS测试指南》为这个方向开了个好头。我期望能够有越来越多的测试者(包括开发者)愿意深入到这个方向中,研究如何能够不断提高移动应用开发的效率和质量,帮助组织以最小的成本实现目标。我也更期望有越来越多的人愿意用图书的方式记录和分享自己的心得,为这个值得重视的领域添砖加瓦。段念豆瓣工程副总裁2014年3月9日于北京致谢

本书的成稿离不开许多人的帮助和支持。首先是家人的支持。对于一个1岁多孩子的父亲,利用业余时间完成一本书的编写是非常困难的。我美丽的妻子不但承担了日常所有的家务,还要独立养育孩子,甚是辛苦。如果没有她的理解和支持,本书与读者见面的时候可能iPhone 9都发布了。同样,我还要感谢我的父母、岳父、岳母和朋友,没有他们的鼓励我同样不会完成本书。

当然,还要感谢侠少和任晓露编辑。侠少负责扮黑脸,认真严格地对初稿进行修改,修改意见一度多于书稿字数。虽然那些改进意见都是正确的,但是由于数量过多,有时无法接受。这时晓露编辑就出现了,并且给了我无限的鼓励。就是在这样的不断重复中,我完成了书稿。在此,真心感谢博文视点的每一位编辑。

对本书中的iOS测试方法,我在豆瓣的工作期间几乎都实践过。在此,我还要感谢豆瓣的每一位移动开发工程师。因为没有你们高质量的代码,我不可能玩出这么多花哨的小技巧。当然,最应该感谢的是解彦博和耿新跃两位老师。如果不是当初两位老师给了我去豆瓣工作的机会,本书的成稿更是无从谈起。

最后,感谢本书的每一位读者,感谢你们对本书的关注和支持。前言为什么要写这本书

随着iOS应用开发的持续火热,iOS测试越来越受到重视。但是,由于其生态系统的封闭性,导致iOS测试方面的资料非常少并且难以搜索。在一次技术交流会上认识了博文视点的任晓露老师,她鼓励我应该写一本iOS测试方面的书。这种约稿一般都会在第1次时被拒绝。我同样拒绝了任老师的约稿,不为别的,只是因为自己水平有限。

我在平时的工作中还是不断地搜索着那些零星的资料,并且发现在iOS测试方面没有任何书籍,国内没有,国外貌似也没有。又是一次技术交流会,又见到了任晓露老师,当然,又谈到了出书的事情。这次互换了联系方式,并且在之后认真考虑了出书的事情。

之后在老婆大人的鼓励下,决定写一本iOS测试方面的书。心想只要动作快就是国内的第1本iOS测试书籍,全当是抛砖引玉了,谁让我在豆瓣的ID是厚脸皮呢,就厚着脸皮写了出版吧。本书的内容

在测试领域内,分歧不断,争论不断。在如何做测试、测试的目的是什么等问题上都会有很大的争议。而测试活动本身受业务需求和团队能力等因素的影响,也会有很大的不同。本书抛开争论和不同,只谈技术相关的问题,通过简单的实践介绍了通过某些工具或者框架来对应某一些测试类型。

第1章

简短地介绍了测试和iOS测试,并且对本书涉及的内容范围进行了介绍。

第2章

介绍了iOS开发和测试使用的基本工具。

第3章

本章首先介绍单元测试的工具,之后通过实践,详细介绍了基于MVC模式的单元测试的使用方法,其中包括针对Model、Controller和View的基本的测试方法。在实践中使用到了一些高级的断言工具和Mock工具。最后再次针对这些工具进行了详细介绍。

第4章

提到UI自动化测试,第一入手点必须是官方工具。本章通过实践详细介绍了iOS官方的自动化测试工具——UI Automation,不但有实践的应对和基本API的讲解,还加入了笔者对UI自动化的总结和第三方工具的简单介绍。希望能做到深入浅出。

第5章

iOS程序不只有Native应用,还有Web应用。本章结合笔者的工作经验和总结,介绍了iOS Web自动化测试的最佳实践,并且从组成结构上剖析了当下最流行的Appium和WebDriver。

第6章

持续集成是现代软件开发的一种体现。没有持续集成的自动化测试都是半自动化测试。本章不但介绍了通用的持续集成工具,还基于之前章节的实践成果,进行了iOS持续集成方面的介绍。

第7章

除了功能测试之外,iOS程序还需要很多的专项测试,例如兼容性测试等。本章主要介绍了几种通用的专项测试类型和方法。

第8章

iOS自动化测试有很多第三方的开源工具。本章从工具本身的技术特点和实现原理上对工具进行了分类,并且对每一类工具选出了佼佼者进行实践介绍。当然,读者可以根据本章的内容写出自己喜欢的自动化工具。

第9章

在2013年的第4季度,Apple公司大爆发似地发布了开发工具Xcode 5、手机操作系统iOS 7和Mac操作系统OS X 10.9。这一系列工具的发布,也带来了测试方面的一些新特性的引入。本章结合之前的内容,针对这些新特性进行了补充介绍。第1章 软件测试与iOS测试iOS测试是软件测试的一个分支,有着软件测试的一些共性。同时,由于iOS平台及其生态系统具有特殊性,iOS测试也有很多自己的特性。本章将从共性与特性两个方面来总结一下iOS测试。1.1 什么是软件测试

业界对软件测试及其目的、手段和方法是什么,一直存在很大争议。作为一家之言,本书尝试对软件测试作出如下描述:软件测试的目标应该服从于软件项目的目标,虽然它不直接产生有价值的成果,但是可以通过建议使用更高效的方法和工具,提升软件开发效率及软件开发质量;还可以通过一些手段,更早、更快、更多地发现缺陷,从而降低这些缺陷可能带来的风险。

基于这一观点,书中会介绍一些可能不包括在传统测试范围内的内容,例如iOS平台的持续集成、iOS相关的打包和发布等。以下将围绕软件测试概念中较有争议的3个方面来阐述本书的观点。1.1.1 测试活动何时展开

在软件产品开发中,测试越早,发现问题后解决问题的成本就越小。如果开发过程中的各个阶段所写的代码都能够正确且稳定地运行,那么在后期将它们集成起来或加入新部件时,相比于项目结束后再一次性执行所有测试,所产生的错误自然要少。尽早并且持续地反馈问题,对于QA来说是一项很重要的职责,对于一个iOS应用也同样重要。具体而言,开发工程师如能在提交一次代码后立刻发现新版本iOS程序中导致应用程序崩溃的问题,则这时修复问题的成本最低。首先,由于发现及时,问题可能涉及的代码非常有限,开发工程师可以很轻松地定位问题;其次,开发工程师对代码的熟悉程度很高,能快速并且完善地解决问题。试想一下,3~5天之后代码库中已经积累了很多次的代码提交,这时再去定位问题并且修改已经是一件比较困难的事情。情况再糟一些,如果两个月以后才发现问题,则该问题可能已经给团队带来了很多的风险,修复这个问题的成本也会很高。

以上只是从时间这个维度来说明问题,除此之外,在实际工作中随着时间的推移,产品的功能和代码量都是在不断增加的。随着代码量的增加,修正软件缺陷的难度也在增加。1.1.2 软件测试与软件缺陷

软件缺陷被测试工程师和开发工程师们称作Bug。软件缺陷会导致软件不能正常运行,它的存在会在一定程度上导致软件不能满足用户的需求,甚至有可能破坏或者泄露用户的重要数据。

不管开发人员的水平有多高,不管开发人员多么小心地开发软件,软件缺陷都会悄无声息地存在于软件系统之中。虽然软件测试是为了消灭软件缺陷而生的,但是迄今为止,还没有一种有效的软件缺陷检测机制。软件运行的环境多种多样,其本身逻辑关系的高复杂度和多种多样的数据结构等因素都决定着软件测试活动不可能通过遍历所有的功能和使用场景来发现软件系统中所有的缺陷。

在软件开发的每一个环节中都可能把软件缺陷引入系统中,通过测试只能发现部分缺陷,并不能检测到系统中的所有缺陷。1.1.3 软件测试与软件质量

软件系统的质量应该更多取决于这个软件系统的生产者——开发工程师。如果开发工程师技术高超并且使用了正确的软件开发方法,软件系统的质量会更可靠。套用一句话:“一个高质量的软件系统是[1]设计和开发出来的,并不是测试出来的”。

那么,软件测试不重要吗?当然不是。一方面,测试可以做到对缺陷的预防。测试工程师要去总结一些项目产品开发活动中非常好的软件工程实践,并且推广到项目组中,建议开发工程师使用更加正确的软件开发方法,通过在开发阶段采用一些科学的管理手段和正确有效的开发方式,来降低软件缺陷的引入数量。另一方面,测试需要对缺陷的检测。工程师可以尝试通过一些持续集成的手段,尽早地开展测试活动,还可以加入自动化测试技术,通过不断、反复地测试来发现更多的缺陷。这两个方面都是有效提高软件质量的重要手段。1.2 软件测试的类型

软件测试按照测试类型,可以划分为:单元测试(Unit Tests)、集成测试(Integration Tests)和系统测试(System Tests)。下面将分别详细阐述。1.2.1 单元测试

单元测试是指对软件系统中最小可测试单元进行的检查和验证。对于“单元测试”中“单元”的解释,要根据实际情况去判定,一般来说是指功能不可再分割的模块或者函数。

单元测试在软件开发流程中占有一席之地。在过去的十几年中,单元测试框架的开发者们不断地完善测试技术,并且建立了一些新手段,用以将单元测试活动集成于软件开发的流程之中。最早的单元测试框架出现在Smalltalk语言中,单元测试的框架为SUnit,由Kent Beck发明。他也是JUnit测试框架的创始人,之后JUnit取得了巨大的成功,单元测试方法也开始流行起来,慢慢地在各个语言版本中都有了单元测试的身影,逐渐形成了xUnit体系。在Objective-C语言中最早的xUnit测试框架是OCUnit。本书将在后面的章节中简要介绍如何使用OCUnit来写单元测试,并会介绍几款类似于OCUnit的单元测试工具。1.2.2 集成测试

集成测试是单元测试的逻辑扩展,最简单的形式是把两个已经测试过的单元组合成一个组件,并测试它们之间的接口。从这层意义上讲,组件是指多个单元的集成聚合。在现实方案中,许多单元组合成组件,而这些组件又聚合成程序的更大部分。方法是测试片段的组合,并最终扩展成进程,将模块与其他组的模块一起测试。最后,将构成进程的所有模块一起测试。

在iOS软件开发中,集成测试主要被简单地分为API接口测试和iOS功能集成测试。API接口测试是指,若一个iOS程序以网络请求的方式使用到了后台服务的功能,测试时需要验证网络的请求以及响应是否符合预期。iOS功能集成测试其实就是功能测试。在一个iOS程序中,有很多功能是在UI界面上体现的,所以在功能测试中测试的重点将会是UI的测试。本书将分别讲述在iOS系统中如何自动化测试iOS的本地应用程序(Native Application)和iOS的Web应用程序(Web Application)。1.2.3 系统测试

系统测试(System Test)是将已经确认的软件、计算机硬件、外设、网络等其他元素结合在一起,进行信息系统组装测试和确认测试。系统测试是针对整个产品系统进行的测试,目的是验证系统是否满足需求规格的定义,找出与需求规格不符或相矛盾的地方,从而提出更加完善的方案。系统测试发现问题之后要经过调试找出错误的原因和位置,然后进行改正。

在iOS平台上,系统测试阶段需要从硬件环境和系统平台两个角度分别进行系统确认测试。在硬件环境方面主要考虑网络环境、所支持设备中性能较差的设备及屏幕分辨率等硬件环境因素。在系统平台方面,主要考虑的是在不同系统平台上的表现是否相同。1.3 iOS平台的一些特性

为了能更有针对性地设计出优秀的测试用例,我们首先需要从平台特性的角度去了解iOS平台的一些特性,在今后的测试工作中也可以围绕着这些特性设计一些很有针对性的测试用例。iOS平台是一个智能移动操作系统,主要具有以下几个方面的特性。

1. 硬件资源方面的特性(1)只有一个应用程序正在运行,并且程序展现时只有一个窗口。在台式机和笔记本电脑的操作系统中,多个程序可以同时运行,并且可以分别展示多个窗口。一般情况下在iOS系统中,一个时间段内只有一个程序在运行并且全屏展示。(2)有限的内存和CPU。不管手机方面的硬件如何快速发展,在相对固定的一段时间内,内存和CPU方面的性能是无法达到台式机和笔记本电脑的水平的。因此,在iOS系统上运行的应用程序需要更高效的代码来执行任务。(3)多样化不稳定的网络接入,在手机方面的网络接入点是可变化的,有以WiFi为接入点的情况,还有用手机卡上网的情况。在用手机卡上网时,还可以根据网速和信号等方面有更细致的分类。台式机和笔记本电脑没有这样多种选择的网络接入,更不会存在接入点随时变化的情况,并且在多数情况下,现在的台式机和笔记本电脑的网络接入是非常稳定的。(4)多样化且不同尺寸的屏幕。由于iOS平台上的应用程序多数都是全屏显示的,所以屏幕的大小决定了应用程序的展示方式。在iOS平台上,用户可能持有不同分辨率屏幕的手机,还可能不断地进行横竖屏切换来调整使用应用程序的方式。

2. 用户使用时的一些特性

用户在使用手机应用程序时,一般都会以快捷而简便的方式完成,这些操作需要在10秒或者更短的时间内得到响应,响应时间过长会影响用户体验。另外,用户每天可能会多次打开相同的应用程序,而且每次使用应用程序的时间也可能非常短暂,这种使用方式对程序的性能提出了更高的要求。

用户使用手机时,可能会使用语音输入,可能会晃动手机,也可能直接使用地理位置信息等,但是在台式机和笔记本电脑方面,普通用户输入的绝大多数信息都集中在键盘这个输入设备上。手机则有着比笔记本电脑和台式机方面更加复杂和丰富的数据输入。处理丰富的输入方式,是iOS开发者的一个小难题。1.4 iOS测试需要做什么

作为一个终端平台,iOS和普通的PC平台一样,用户可以使用本地的应用程序,也可以使用浏览器访问Web站点。所以,本书会分别介绍一些iOS本地的应用程序测试方法,以及如何在iOS平台上测试Web应用程序。不管是什么形式的程序,或多或少都会有一些后台服务,每一个组件都会有单元测试和集成测试的步骤。

从分层测试的角度来看,一个iOS应用可以分为前端展现和后台服务两个部分。本着从iOS特性出发的目的,本书主要会涉及iOS平台的前端展现部分。前端展现部分按照测试阶段来划分,又可以分为单元测试、集成测试和系统测试;按照UI展现的方式划分,可以分为本地应用程序和Web应用程序。所以,我们通过表1-1来说明本书所涉及的内容和相关iOS平台的测试技术。表1-1 iOS平台测试范围表

[1]这句话在很多软件测试方面的教材中都有引用,已经完全找不到最早的出处。第2章 iOS环境准备工欲善其事,必先利其器。由于Apple公司对iOS开发系统采用了非常封闭的策略,所以在对iOS程序进行开发和测试的时候,使用官方推荐的软件和硬件设备会让整个开发测试过程更顺畅。2.1 开发测试设备

在开启iOS测试或者开发之前,一台Macintosh的计算机是必不可少的。相关的购买信息可以在Apple公司的官方网站上查到,如图2-1所示。图2-1 Apple官网相关Mac的销售信息

当有了Mac(Macintosh的简称,本书以下部分使用Mac来表示Macintosh)以后,本书建议使用OS X Mountain Lion 10.8以上的操作系统。可以通过查看“关于Mac”这个选项来查看Mac操作系统的系统信息,如图 2-2所示。图2-2 Mac系统信息2.2 安装和设置Xcode

什么是Xcode?Xcode是苹果公司向开发人员提供的集成开发环境,用于开发Mac OS X和iOS应用程序,它运行于苹果公司的Mac操作系统下。Xcode不只是一个集成开发环境,还是一个开发工具套件集合。本书将要介绍的UI自动化工具,也是在Xcode套件中的一个工具。

安装Xcode很简单,只需在App Store里面下载就好,系统会帮你自动安装。Xcode安装完成以后,需要进行一些简单的补充设置:首先,需要开启Xcode,在Xcode的菜单中,单击Preferences(设置)选项,会弹出Xcode系统信息设置窗口,如图2-3所示。在这个弹出的窗口中单击“Downloads”菜单项,跳转到Xcode系统信息相关下载的设置窗口,如图2-4所示。在Downloads页面中选择Components并且下载Command Line Tools(必须下载),并且最好下载 iOS Simulator的 5.0和5.1版本。在Documentation中选择下载和iOS开发相关的文档。在下载结束后,可以正式开始iOS测试的步伐。图2-3 Xcode系统设置信息图2-4 Xcode系统设置信息中“Downloads”页面的展现2.3 iOS开发者证书

什么是iOS开发者证书?如果想要创建针对iOS设备的应用程序,或者在iOS设备上调试程序,都必须具有开发者证书。开发者证书是iOS开发者的凭证,需要在Apple官网上注册缴费,对于一般开发者来说年费是99美元。想要把自己的应用发布到App Store来提供给更多的人使用时,必须拥有开发者证书。

本书绝大部分内容都可以在Xcode自带的iOS模拟器上完成。没有iOS开发者证书,基本不会影响到你的学习。但是如果有开发者证书,则会是一个更完美的配置。2.4 知识的准备

本书将会以iOS测试为主线,展开一些实践型测试活动的介绍和探索,主要针对有一些测试理论基础的读者。另外,在iOS单元测试和UI自动化测试方面都会涉及程序设计和编码实践的相关技术。如果希望更加深入地学习,可能需要一些相关编程语言的基础,例如Objective-C、JavaScript,可能还需要Java。本书在介绍单元测试时,相关的实例代码采用的编程语言是Objective-C;在介绍UI自动化测试时,会使用很多JavaScript代码;在介绍iOS平台的Web测试时,还会给出一些Java代码的例子。

不要被以上所列计算机语言的数量所吓到。在自动化测试领域,编码用到的都是一些计算机语言的基础概念,不需要掌握其高级特性。即使之前没有代码经验,也不用担心,在遇到编码相关的问题时,可以通过查阅资料和独立完成一些实践练习的方法来迅速提高编程能力。第3章 iOS单元测试单元测试是一种测试手段,其价值在Web开发过程中已得到体现和证实,笔者坚信,单元测试实践同样会在iOS平台上发挥巨大的作用。但由于iOS平台还是一个发展中的平台,单元测试的平台工具和手段都还需要充实,并且单元测试的相关实践还需要总结和沉淀,所以iOS的单元测试实践在国内相对滞后一些。可喜的是近期在国外一些知名的技术论坛上已经涌现出了一些优质的iOS单元测试的教程供大家学习,伴随着其不断地发展和完善,iOS单元测试的可执行性和重要性会慢慢地体现出来。本章主要介绍单元测试的相关工具和单元测试的基本套路。3.1 单元测试工具

说到单元测试,必须先说一下单元测试的工具。在iOS开发的过程中使用的语言是Objective-C。对于这门语言来说,单元测试工具有很多款。本章会选取1~2款使用广泛并且功能强大的单元测试工具进行详细的功能阐述。3.1.1 OCUnit

OCUnit是现在Xcode自带的单元测试工具,也是Objective-C语言使用最广泛的单元测试工具。先通过一个“Hello_OCUnit”的测试用例来初步认识OCUnit的使用方法。

首先启动Xcode开发环境并且新建一个项目。在如图3-1所示的项目配置中选择 “Empty Application”,单击“Next”按钮后你将会看到如图3-2所示的情景,我们需要在项目的名称中填写“Hello_OCUnit”,然后在项目的设置中,选择“Include Unit Tests”和“Use Automatic Reference Counting”选项。先来解释一下这两个选项的意思。“Include Unit Tests”的意思是包含单元测试,这个选项被选中后,在Xcode中会自动给开发者建立一个 Unit Test项目,方便开发者开发单元测试。“Use Automatic Reference Counting”的中文意思是“使用自动引用计数技术”,自动引用计数技术是在iOS 5.0以后引入的一个新特性,可以帮助开发者更方便地管理内存。相关自动引用计数技术(ARC)的一些详细内容可以参考Apple官方文档。本章的所有实例项目都会使用ARC技术。图3-1 新建项目选择项目类型图3-2 填写项目名称等信息

单击“Next”按钮后,在选择项目保存的地方建立项目,项目建立成功后如图3-3所示。图3-3 新建项目的概要信息展示

当单元测试的项目建立好以后,Xcode会自动生成一段单元测试的代码,无须任何改动即可运行。先来看看运行后的效果如何,单击Test菜单项运行单元测试,如图3-4所示。图3-4 运行测试的菜单选项

在选择运行单元测试以后,Xcode会自动编译代码,并且运行测试。稍等片刻,即可得到单元测试的执行结果。如图3-5所示,Xcode自动标示测试失败的具体位置,并且有详细信息说明。测试结[1]果由失败(红色)变为成功(绿色)的过程,是单元测试发挥关键作用的时刻。在得到测试失败的结果以后,需要迅速做出反应,修改相关的代码并且保证测试通过。图3-5 测试结果的界面展示

在修改代码保证测试通过之前,需要了解OCUnit的使用方法和单元测试的基本套路。如图3-6所示,Xcode自动生成的单元测试代码位于Hello_OCUnitTests这个目录下。图3-6 项目代码树展示

分别选择.h文件和.m文件进行查看:// Hello_OCUnitTests.h#import @interface Hello_OCUnitTests : SenTestCase@end

在Hello_OCUnitTests.h文件中定义了一个单元测试类Hello_OCUnitTests,它继承于SenTestCase类,所以Hello_OCUnitTests类就有了SenTestCase类中的方法和属性。具体的属性和方法,稍后介绍。// Hello_OCUnitTests.m@implementation Hello_OCUnitTests- (void)setUp{ [super setUp]; // Set-up code here.}- (void)tearDown{ // Tear-down code here. [super tearDown];}- (void)testExample{ STFail(@"Unit tests are not implemented yet in Hello_OCUnitTests");}@end

这个单元测试的实现文件虽然代码量非常少,但是它还是演示了OCUnit的几个特性。(1)要创建一个测试,需要编写一个方法来表示这个测试。Hello_OCUnitTests实例中的测试方法是“testExample”,测试方法以“test”为方法名的前缀,遵守了OCUnit的命名约定,可以让OCUnit自动发现和执行该方法。换言之,在测试用例文件中,不以“test”为前缀的方法,不会被OCUnit发现和执行。(2)测试需要有一个地方放置,在Hello_OCUnitTests实例中,Hello_OCUnitTests是一个类,它继承了OCUnit框架提供的测试用例基类SenTestCase。关于SenTestCase,之后会有更详细的描述。只有直接或者间接地继承了SenTestCase类以后,OCUnit才能去发现和执行测试方法。(3)测试一定要有预期结果,在测试方法中表达预期结果会使用断言。OCUnit提供了很多的断言方法。在本例中,Xcode自动生成的是“STFail()”这个断言。从方法名称来看,这是一个永远都不会变绿的断言,也是实例测试执行失败的原因。之后我们换一个断言来保证测试成功。(4)当OCUnit执行一个测试方法时,会按顺序执行方法中每一行语句。在执行的过程中,方法抛出了非预期的异常,会被立刻停止,并且结果为失败。相反,在完整地执行方法以后,测试就是成功的。在测试方法中,绝大多数的异常都来自于断言,如果断言成功,则不会有异常抛出。[2](5)再说测试用例(TestCase)。一个测试用例中可以有多个测试方法,也可以只有一个测试方法。在每个测试方法执行之前,OCUnit会自动调用setUp方法。同理,在测试方法执行以后,tearDown方法会执行。

在一行代码也没有写的情况下,得到的这段单元测试代码需要进行简单的修改才能保证测试结果是绿色的。找到之前代码中的:STFail(@"Unit tests are not implemented yet in Hello_OCUnitTests");

并且修改为:STAssertFalse(NO, @"Hello OCUnit");

再次运行测试,当测试运行完成以后,之前的那个错误不存在了,但是好像也不能看到更直接的测试运行结果。这时,我们需要在View的方式中选择显示控制台输入,查看控制台日志:Test Suite '/Users/komejun/Library/Developer/Xcode/DerivedData/Hello_OCUnit-gdfhaoxzgruccjbgobrtlucivpxd/Build/Products/Debug-iphonesimulator/Hello_OCUnitTests.octest(Tests)' finished at 2013-03-06 16:22:31 +0000.Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.000) secondsTest Suite 'All tests' finished at 2013-03-06 16:22:31 +0000.Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.001) seconds

测试结果图形化的界面展示如图3-7所示。图3-7 测试结果的图形界面展示

从测试运行的结果中可以看出,单元测试只运行了一个方法,并且测试结果为绿色。这样,第1个单元测试就简单完成了。通过这样一个简单的小练习,希望大家能对单元测试有一个最初步的印象,并且对Xcode的使用渐渐熟悉。如果还没有开始动手做练习的话,赶紧尝试一下简单的练习吧。

自己做练习时,还可以使用其他断言函数,让自己快速地对OCUnit有一些更深入的了解。3.1.2 GHUnit

GHUnit是Objective-C语言里的另外一种单元测试工具。在Xcode 4之前,OCUnit的功能限制太多了,在单元测试这个层面上,可以使用OCUnit来做的工作实在有限,所以同期有很多单元测试工具涌现出来,其中有GHUnit和即将出场的GTM等工具。

在Xcode 3.x时代,GHUnit不仅仅在功能方面领先于OCUnit,并且还对OCUnit进行了兼容。GHUnit的主页面:http://gabriel.github.com/gh-unit/。需要说明的是,GHUnit还是一个开源项目,如果你觉得GHUnit在某些方面还不太让你满意的话,可以直接修改GHUnit的源代码,从而实现自己想要的功能。

先通过一个非常简单的例子了解GHUnit的使用方法。首先,需要在Xcode中新建一个项目,项目的名称为“Hello_GHUnit”。在项目选项中,只选择使用ARC功能,不选择“Include Unit Tests”,如图3-8所示。图3-8 新建项目设置

项目设置完毕以后,选择项目的保存路径(路径可自定义)。在“Hello_GHUnit”项目中选择Add一个Target,把这个Target命名为“tests”,同样不选择“Include Unit Tests”这个选项,如图3-9所示。Target建立成功以后,左侧导航栏如图3-10所示。

GHUnit相关的Target已经建立好了,可以开始设置并且使用GHUnit单元测试框架。图3-9 新建Target设置图图3-10 新建Target完成以后左侧导航栏显示

第1步,加入framework包。选择好Target以后,在Build Phases中选择在“Link Binary With Libraries”中添加[3]“GHUnitiOS.framework”和“QuartzCore.framework”,如图3-11和图3-12所示。图3-11 Target设置Build Phases图3-12 通过选择外部文件来加载GHUnit的framework。

第2步,修改编译设置。在“Build Setting”中搜索“other linker Flags”,找到并且设置其值为“-ObjC -all_load”,如图3-13所示。图3-13 设置“Other Linker Flags”

第3步,修改程序入口main.m文件。从程序入口处更换需要启动的Delegate。如果在这个方面还是没有思路的话,也可以直接使用以下main.m的代码,直接使用新的main.m文件替换原来的main.m文件。//main.m#import #import int main(int argc, char *argv[]){ @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([GHUnitIOSAppDelegate class])); }}

第4步,添加一个GHUnit的测试用例。首先需要在tests下面添加一个文件,如图3-14所示,选择一个“Objective-C class”,并不是一个“Objective-C testcase class”,设置新建的测试用例继承于GHTestCase,并且命名为“myTest”。图3-14 新建GHTestCase

开始测试用例的功能完善。在myTest.m文件中,内容是这样的。#import @interface MyTest : GHTestCase { }@end@implementation MyTest- (void)testStrings { NSString *string1 = @"a string"; GHTestLog(@"I can log to the GHUnit test console: %@", string1); NSString *string2 = @"a string"; GHAssertEqualObjects(string1, string2, @"A custom error message. string1 should be equal to: %@.", string2);}@end

经过了这一步步的设置,基于GHUnit的单元测试就可以运行了。单击“运行”按钮,如图3-15所示,GHUnit在模拟器中运行,单击模拟器中“Run”按钮,单元测试就被执行了。如果希望看到一些错误的结果,可以在稍后的练习中,添加一些运行出错的代码,或者直接写一个错误的断言等。图3-15 GHUnit运行结果

总结:进行了一些简单的设置和修改后,GHUnit的单元测试已经可以运行起来了。需要注意的是,GHUnit是以一个应用程序的形式被安装到手机里运行的,在这一点上和后续将要介绍的GTM非常相似,和之前的OCUnit则完全不同。3.1.3 GTM

GTM(Google Toolkit for Mac)是Google主导开发并开源的一款Mac和iOS的单元测试工具。虽然GTM的代码已经很久没有更新了,但是仍有很多人在使用。更多人会把GTM当做一个工具集合来使用,项目中很多如Base 64等功能文件,至今还在被广泛使用。GTM的使用方式和GHUnit类似,可以根据官方文档进行设置,在这里就不做相关的代码演示了。

相关的单元测试工具还有很多,其中包括框架层面的“CATCH”等,还有辅助单元测试的mock工具和持续集成工具(mock工具和持续集成工具将会在后续章节中单独介绍),在这里就不一一列举了。本书更希望能通过一些小的实践类的项目来教会大家如何在一个具体的项目中正确有效地使用这些工具。3.2 单元测试实践

3.1节主要介绍了单元测试框架级别的工具,重点介绍了两款单元测试工具:OCUnit和GHUnit。在本节将开始单元测试方面的实践。在两款单元测试工具中,本书选择了OCUnit作为后续项目实践的测试工具。主要原因如下:(1)OCUnit和Xcode集成程度高,使用方便;(2)OCUnit现在直接由官方维护,和iOS平台的发展步伐一致。

在开始单元测试实践之前,还需要再增加一些理论方面的知识。被单元测试的对象或者系统,我们简称为SUT(System Under Test,被测试系统)。SUT的形态大概可以分为三种:(1)有明确的返回值。在做单元测试时 ,只需调用这个方法,然后验证方法的返回值是否符合预期结果。这种方法可以被称作返回值验证法。返回值验证法最典型的使用场景是测试一个四则运算的结果。(2)没有返回值。在调用了这个方法时,这个方法只改变其对象内部的一些属性或者状态,方法本身没有返回值。这种方法可以被称作状态验证法。状态验证法最典型的使用场景是测试一个对象的set方法。(3)SUT依赖于外部的一个类或者方法,它会调用外部的一个方法,被SUT依赖的外部的方法可能有返回值也可能没有返回值。这种方法可以被称作“行为验证法”。行为验证法重要的一方面是验证在SUT内部是否发起了某种行为,需要通过直接或者间接的手段来验证SUT是否发起了预期行为。但是,这个行为本身功能的正确性是由其他测试方法来保证的,并不需要通过行为验证法来保证。一个单元测试最好只验证一个功能点,这样可以使SUT的代码结构更合理,并且单元测试更容易维护。

单元测试的基本方法介绍完了,是时候开始实践了。下面笔者会通过一个具体但非常简单的例子来介绍单元测试在iOS程序中是如何被应用的。3.2.1 实践项目介绍

不管是先有单元测试再有产品代码(测试驱动开发TDD),还是先写好产品代码再来加入单元测试,这两种方式都需要了解我们开发的是一个怎样的iOS应用程序,只有充分了解SUT的业务逻辑和相关处理代码应该怎样实现,才能写出更优秀的单元测试。下面将介绍SUT。

笔者选择了一个iOSCounter的项目,它是一个简单的iOS上面计数的应用程序,用户可以通过“+1”或者“-1”的按钮来改变数字的显示,如图3-16所示。图3-16 iOSCounter应用程序

在iOS应用程序开发中,绝大多数程序都是基于MVC(Model View Controller)模式的,先看一下MVC中的每个部分都是通过一个什么样的机制串联起来的。如图3-17所示。图3-17 MVC模式简图

用户的每一次动作都是以一个View的Action方式传递给Controller,然后Controller再发送消息通知Model做出响应的逻辑处理,当Model层面上的业务逻辑处理有了结果以后,Model会以通知(Notification)的形式通知Controller。这时Controller收到通知以后,会更新View的显示状态。这样一个基于MVC模式的用户动作响应循环就完整地完成了。在我们的iOSCounter中,Model层的业务逻辑非常简单,在实际的工程中很多工程师不会把这么简单的业务逻辑做单独拆分,而是直接在ViewController的一个文件中全部实现。但为了单元测试的学习,我们还是把这么简单的Model层分离了出来。

为了更充分地展现单元测试的方法,还在SUT中加入了保存当前显示数据的功能,这样会给我们的Model层增加一些难度,让单元测试有更多的发挥空间。但这本身是不合理的,不过为了更好地展示单元测试效果,就有了这个牵强的功能设计,还请读者不要过多纠结于这个功能的实现方式,SUT的功能实现方式肯定不是最优化的方案,而只是为了演示单元测试的教学。3.2.2 Model的单元测试

通过刚才的分析,应用程序的Model层只需要有一个Class就好。这个Class的具体的属性和方法定义如下:@interface Counter : NSObject@property (nonatomic) NSInteger count;-(id)initWithUserDefault:(NSUserDefaults *) defaults;-(void)increment;-(void)decrement;-(NSInteger)getCountInDefaults;@end

在Counter这个类中主要有4个方法需要测试,我们需要一步步地进行单元测试的介绍。先从构造方法开始,先来看一下构造函数的写法:-(id)initWithUserDefault:(NSUserDefaults *) defaults{ self = [super init]; if (self) { _defaults = defaults; _count = [self getCountInDefaults]; } return self;}

从上述代码中可以看出,这个构造方法主要就是给两个成员变量进行赋值,并且返回一个对象实例,_dafaults这个成员变量的值取决于外部参数,_count这个成员变量的值是由getCountInDefaults这个方法来决定的。第1个单元测试方法需要验证构造方法的正确性,代码如下:- (void)testInitShouldNotReturnNil{ Counter *counter = [[Counter alloc]initWithUserDefault: [NSUserDefaults standardUserDefaults]]; assertThat(counter,instanceOf([Counter class]));}

在这个测试中,使用了一个断言小工具OCHamcrest(相关OCHamcrest工具的详细介绍会在之后的章节呈现),它可以使断言功能更强大,并且可读性更好。这个测试需要检查通过构造函数得到的counter对象应该是Counter类的一个实例。使用了OCHamcrest以后,即使不懂编程语言的人也可以看得懂这样一句类似于英文的源代码(assertThat(counter,instanceOf([Counter class]));)。构造方法没有太多的测试点,需要把更多精力放到业务逻辑方面,来看看getCountInDefaults方法:-(NSInteger)getCountInDefaults{ NSNumber *reminderId = [_defaults objectForKey:countInDefaultID]; if (reminderId) { reminderId = reminderId; } else { reminderId = @0; } return [reminderId integerValue];}

getCountInDefaults方法主要是把存储在NSUserDefaults中count的数值读取出来,并且以NSInteger类型返回,而且在NSUserDefaults中没有任何存储时,需要返回0。这是一个有返回值的方法,非常不巧的是,它有一个外部依赖——NSUserDefaults,读出来的值是在文件中存储的。如果要读取的文件不存在或者文件中存储的值不是测试方法的期望值,都会影响到测试结果。一个稳定并且健壮的测试不应该受到外部依赖数据的影响,所以在这里需要用到Mock工具把这部分外部依赖mock掉,让测试运行得更稳定。在实例中用到的Mock工具不是OCMock,而是OCMockito。这两款工具功能上都是Objective-C语言的Mock工具,并且在功能方面都很强大。但是笔者个人更喜欢OCMockito,所以使用OCMockito更多一些(不管是现在提到的OCMockito还是之前提到的OCHamcrest,都会在单元测试实践之后展现更多的详细功能)。

在开始写单元测试代码之前,先来梳理一下有依赖的单元测试方面的思路,毕竟这是现在接触到的第1个需要处理外部依赖的单元测试。首先,从功能点的角度来讲,有两条路径需要测试,即在NSUserDefaults中已经存储了值和没有任何值存储这两种情况。为了能够更好地实现测试,我们需要mock一下NSUserDefaults,然后通过控制NSUserDefaults的方法返回值来覆盖以上提到的两种情况。所以针对这个方法,会出现了两个单元测试方法testGetCountInDefaultsWithNilShouldReturnZero(原来存储的值为空需要返回0)和testGetCountInDefaultsWithNumberThreeShouldReturnIntegerThree(原来存储的值为NSNumber类型的3,现在需要返回NSInteger类型的3),在实现具体的测试代码时,有一些在测试方法中重复的代码,可以把这些会重复用到的代码分别放在setUp方法和tearDown方法里,从而减少测试代码中的重复。@implementation CounterTest2{ Counter * sut; NSUserDefaults * mockDefaults;}-(void)setUp{ [super setUp]; mockDefaults= mock([NSUserDefaults class]); sut = [[Counter alloc]initWithUserDefault:mockDefaults];}-(void)tearDown{ sut = nil; [super tearDown];}-(void)testGetCountInDefaultsWithNilShouldReturnZero{ [given([mockDefaults objectForKey:@"currentId"]) willReturn:nil]; assertThatInteger([sut getCountInDefaults],equalToInteger(0));}-(void)testGetCountInDefaultsWithNumberThreeShouldReturnIntegerThree{ [given([mockDefaults objectForKey:@"currentId"]) willReturn:@3]; assertThatInteger([sut getCountInDefaults],equalToInteger(3));}

在测试方法中,[given([mockDefaults objectForKey:@"currentId"]) willReturn:nil]可以通过OCMockito中的这个用法来强制Mock对象的某一个方法返回一个所希望的值。

最后再来示范increment方法的单元测试用例的写法。先要了解increment方法的具体实现方法:-(void)increment{ _count = [self getCountInDefaults] + 1 ; [_defaults setObject:[NSNumber numberWithInteger:_count] forKey:countInDefaultID]; [[NSNotificationCenter defaultCenter] postNotificationName:CounterModelChanged object:self];}

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载