【AICon】探索RAG 技术在实际应用中遇到的挑战及应对策略!AICon精华内容已上线73%>>> 了解详情
写点什么

iOS 开发中的单元测试(一)

  • 2013-06-05
  • 本文字数:3412 字

    阅读完需:约 11 分钟

导读:本文不讨论单元测试是什么,或者它之于一个工程的利弊,我认为单元测试是一个开发者保证产出代码质量的有效工具。本文从使用者的角度对比当下比较流行的两款单元测试框架,给大家提供一些选用建议。如果你还不甚了解单元测试在工程中所起到的作用,或者还不知道TDD的开发模式,可参考: Test-Driven Development Unit Testing

本文对比两个 iOS 开发中常见的单元测试框架:OCUnit,被官方集成进 XCode 4.x 版本中;GHUnit,被推荐最多的测试框架,带 GUI 界面。初窥两款测试框架非常相似,而上手使用就会发现其中的区别。细节上的区别使两款框架在不同角度各有优劣。

OCUnit

OCUnit 是 XCode 4.x 集成的单元测试框架,OCUnit 中的测试分为两类,一类称为 Logic Tests,另一类称为 Application Tests。Logic Tests 更倾向于所谓的白盒测试,用于测试工程中较细节的逻辑;Application Tests 更倾向于黑盒测试,或接口测试,用于测试直接与用户交互的接口。

• 添加单元测试

OCUnit 是 XCode 集成的,所以其与工程的结合理应是最好的,添加到工程中的成本也理应最低。使用 XCode 创建新工程的流程中就有一个“Include Unit Tests”的选项(如图 1),新的工程就会自动生成一个 Logic Tests。

向已存在的工程中添加 OCUnit Logic Tests 也不复杂,只需要添加一个类型为:“Cocoa Touch Unit Testing Bundle”的 Target 即可(如图 2)。

图 2,向已存在的工程中添加 OCUnit 测试

向已有工程中添加一个测试 Target 时,XCode 会自动生成一个 Scheme,运行单元测试用例和 Build 原工程需要切换不同的 Scheme。如果认为切换 Scheme 非常麻烦,也可以在添加 Target 之前,在“Manage Scheme”菜单中取消“Autocreate schemes”(如图 3)。

图 3,添加 Target 不创建 Scheme

Application Tests 要基于 Logic Tests 做一些修改。一般来说一个工程既需要 Logic Tests 也需要 Application Tests,所以建议按照上述方法添加一个单独的 Target,然后执行以下操作(如图 4):

1. 在 Build Settings 中搜索“bundle loader”,设置为:$(BUILT_PRODUCTS_DIR)/APP_NAME.app/APP_NAME(APP_NAME 是应用名)

2. 再搜索“test host”,设置为:$(BUNDLE_LOADER)

3. 在 Build Phases-Target Dependencies 中添加依赖,选择主程序 Target

图 4,添加一个 Application Tests

• 创建测试用例

OCUnit 的测试用例最常用的方法有三个

1. - (void)setUp:每个 test 方法执行前调用

2. - (void)tearDown:每个 test 方法执行后调用

3. - (void)testXXX:命名为 XXX 的测试方法

添加 Target 之时 XCode 已经自动创建了一个测试用例类:UnitTestDemoTests,其中 UnitTestDemo 是工程的名字,该类中已经包含了 setUp,tearDown 和 testExample 三个方法。

通过 command+n,选择“Objective-C test case class”创建一个新的测试用例类(如图 5)。通过 XCode 创建的测试用例类是一个继承自 SenTestCase(OCUnit 由 SEN:TE 公司开发,因此基类命名为 SenTestCase)的空类,需要模仿 UnitTestDemoTests 编写测试方法。

图 5,创建一个测试用例类

开发者可以自己实现无返回值,且命名规则为 testXXX 的实例方法,并使用框架提供的大量断言方法。

Logic Tests 与 Application Tests 的区别主要在 setUp 方法,Logic Tests 只需在 setUp 方法中初始化一些测试数据,而 Application Tests 需要在 setUp 方法中获取主应用的 AppDelegate,供 test 方法调用。

值得注意的是,OCUnit 的 test bundle 是侵入主应用的,因此在使用过程中要十分注意,不要让单元测试的资源覆盖主应用资源,造成诡异的 Bug。

• 运行测试

由于 OCUnit 是集成在 XCode 中的框架,因此在 XCode 中运行也比较方便。切换到单元测试的 scheme(如果与工程共用 scheme 则无需切换),Product->Test(或直接使用快捷键 command+u),框架会自动查找所有工程中 SenTestCase 的子类,运行其中全部命名类似 testXXX 的无返回值方法。

• 测试反馈

OCUnit 的失败方法会通过 Console 和 XCode Issues 两个位置反馈,通过 XCode Issues 可以直接定位到出现错误的单元测试代码行。Issue 的提示信息就是在单元测试断言方法中定义的 description。

GHUnit

GHUnit 是一款 Objective-C 的测试框架,除了支持 iOS 工程还支持 OSX 的工程,但 OSX 不在本文的讨论范围。GHUnit 不同于 OCUnit,它提供了 GUI 界面来操作测试用例,而且也不区分 Logic Tests 和 Application Tests。

• 添加单元测试

与集成进 XCode 的 OCUnit 相比,GHUnit 的添加过程略显复杂。首先在上下载 GHUnit 的框架包,当前的 For iOS 的最新版本是 0.5.6,解压后是一个 GHUnitIOS.framework 的文件夹。

打开已经存在的工程,添加一个 EmptyApplication Target,并在新 Target 中添加刚刚下载的 GHUnitIOS.framework(如图 6、7)。

图 6,在新 Target 中添加 GHUnitIOS.framework

在 Build Phases 中添加非官方框架并不会把框架文件拷贝到工程目录,而是只做一个链接,所以建议在添加之前先把框架拷贝到工程目录下。

图 __7_,选择 __GHUnitIOS.framework_

接下来用相同的方法添加框架依赖的其他库:“QuartzCore.framework”。

在 Build Settings 中搜索“linker flags”,设置 Other Linker Flags - Debug - 添加一个支持全架构和全版本 SDK 的标示“-ObjC -all_load”(如图 8)。

图 __8_,设置 __linker flags_

删除 Tests Target 中的 AppDelegate(.h 和.m 一起删除)。修改 main 函数,支持 GHUnitIOS,导入 GHUnitIOSAppDelegate 代替原来的 AppDelegate,修改 UIApplicationMain 的参数(如图 9)。

图 __9_,修改 __main__ 函数 _

至此已经完成了 GHUnit 的添加,选择新建 Target 同时创建的 scheme,直接 Build and Run 即可在设备或 Simulator 中启动一个新的 App(如图 10),即该单元测试的 App。

图 __10_,单元测试 __App_

• 创建测试用例

创建 GHUnit 测试用例与创建 OCUnit 测试用例相似。

新建一个 Objective-C Class 文件,继承自 GHTestCase,在 XCode 生成的.h 文件中不会导入 GHUnit.h 文件,需要开发者自行导入“#import <GHUnitIOS/GHUnit.h>”。

GHUnit 框架提供断言方法比 OCUnit 更加丰富,开发用例也就可以做的更加细致,更有利查找 / 定位错误。

测试方法的命名规则与 OCUnit 一样,是以 test 开头的无返回值方法:- (void)testXXX。而常用的方法除了上述提到的 setUp 和 tearDown,GHUnit 还提供了 setUpClass 和 tearDownClass 两个方法,在该用例运行前和结束后调用。另外,刚刚提到 GHUnit 不区分 Logic Tests 和 Application Tests,所以在 setUp 和 tearDown 方法中也就不存在设置的区分。

• 运行测试

运行 GHUnit 需要分两步,首先编译并安装单元测试 App 到设备或 Simulator 里(如图 11),创建了两个用例,每个用例中分别有一个方法。

图 __11_,两个用例的 __GHUnit App_

在 App 中可以通过点击右上角的 Run 按钮运行全部用例,框架会查找所有以 testXXX 命名的无返回值方法,并执行。或点击 TableView 中的某个 Cell 运行单独的测试方法。

• 测试反馈

断言失败测试未通过的方法在 App 中会标记为红色,并给出每一个方法的运行时间。在 Console 中会打印出详细的出错信息,包括:异常类型,出错文件,位置,以及断言方法中指定的出错原因。更重要的是,出错时的程序堆栈内容(如图 12)。

图 __12_,未通过测试的方法,_Console__ 中的内容

GHUnit 通过 Console 中的内容给开发者提供帮助,可以快速定位程序出错的位置,这一点比 OCUnit 做的要好。

总结

GHUnit 在安装上确实显得有些麻烦,无法跟集成在 XCode 里的 OCUnit 相比。 但从开发者的角度讲,我更喜欢 GHUnit 带来的体验,GUI 的操作界面可以脱离 IDE 单独运行,支持运行单一测试方法和运行全部用例的,打印出错堆栈可以更快定位到问题所在。

本文简单介绍了两款框架的安装与入门,可以初步了解其各自特点,在接下来的文章中将会更加详细的介绍如何使用框架进行单元测试,以及框架中的一些高级功能。此外,后续还将向大家介绍另外的与这两款框架区别更加明显的单元测试框架。

作者简介:

高嘉峻(微博: @gaosboy ),SegmentFault.com 联合创始人,杭州 iOS 开发者沙龙发起人,资深 iOS 开发者。


感谢李永伦对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2013-06-05 08:4732208

评论

发布
暂无评论
发现更多内容

“三段三域法”应用架构模型

凌晞

架构 架构设计 技术架构

我就不服了,看完这篇文章,5大常见消息队列开发你还学不会

小Q

Java 编程 程序员 开发 消息队列

技术解码 | 玩转视频播放,自适应码流技术

腾讯云音视频

音视频 转码

晨间日记的奇迹

熊斌

读书笔记

万万没想到!ModelArts与AppCube组CP了

华为云开发者联盟

AI 技术 华为云

MySQL-技术专题-Join语法以及性能优化

洛神灬殇

Redis-技术专题-数据结构

洛神灬殇

深圳派发数字人民币红包!个人数字人民币钱包即将亮相

CECBC

数字货币 数字人民币

Java 客户端操作 FastDFS 实现文件上传下载替换删除

哈喽沃德先生

Java 文件系统 分布式文件存储 fastdfs 文件服务器

高难度对话读书笔记——目的篇

wo是一棵草

PanDownload复活了!60MB/s!附下载地址

程序员生活志

PanDownload 网盘 下载器

SpringBoot 实战:如何优雅的处理异常

看山

springboot 实战 优雅响应

2020南京国际工业互联网及工业通讯展览会

InfoQ_caf7dbb9aa8a

2020第十三届南京国际智慧工地装备展览会

InfoQ_caf7dbb9aa8a

架构方法论之“极限审视法”

凌晞

架构 方法论 设计思维

MySQL-技术专题-SQL性能分析

洛神灬殇

架构师训练营第一期 - 第四周课后 - 作业二

极客大学架构师训练营

第3周作业提交

饭桶

第3周学习总结

饭桶

2020第十三届南京国际智慧新零售暨无人售货展览会

InfoQ_caf7dbb9aa8a

2020第十三届南京国际智慧停车展览会

InfoQ_caf7dbb9aa8a

2020第十三届南京国际大数据产业博览会

InfoQ_caf7dbb9aa8a

从戚家军看组织战斗力塑造(组织的六脉神剑)

凌晞

组织

MySQL-技术专题-实战技巧

洛神灬殇

什么是 Kubeless?| 玩转 Kubeless

donghui

Kubernetes kubeless

技术革新的脉络及趋势

凌晞

技术 进步

手把手教你锤面试官 04——假装精通redis

慵懒的土拨鼠

轻言业务架构图

凌晞

架构 企业架构 架构设计 架构设计原则 业务架构

转型敏捷123

研发管理Jojo

SpringBoot-技术专题-@Async异步注解

洛神灬殇

2020南京国际人工智能产品展览会

InfoQ_caf7dbb9aa8a

人工智能

iOS开发中的单元测试(一)_Android/iOS_高嘉峻_InfoQ精选文章