【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

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:4732220

评论

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

嵌入式程序调用函数的内部过程和机制

不脱发的程序猿

单片机 嵌入式程序 嵌入式设计

Linux C/C++ 学习路线总结!助我拿下腾讯offer

赖猫

后台开发 C/C++ Linux服务器开发

揭秘 Amazon Go 无人商店是如何炼成的!

亚马逊云科技 (Amazon Web Services)

MapReduce排序以及序列化

五分钟学大数据

大数据 hadoop mapreduce

Nginx负载均衡配置误区

运维研习社

nginx 负载均衡 5月日更

anyRTC 六周年 打造全网最低音视频价格

anyRTC开发者

音视频 WebRTC RTC sdk

堪称完美!淘宝内部百亿级Java高并发系统架构设计PDF手册分享

Java架构追梦

Java 架构 高并发 淘宝网 亿级架构设计

更灵活的边缘云原生运维:OpenYurt 单元化部署新增 Patch 特性

阿里巴巴云原生

容器 运维 云原生 中间件 边缘计算

为啥你写的代码总是这么复杂?

华为云开发者联盟

软件 代码 代码注释 bug 复杂度

“云演唱会”也有仪式感!能检票、可转赠,爱奇艺“云票”如何重构线上购票逻辑

爱奇艺技术产品团队

如何高效地存储与检索大规模的图谱数据?

华为云开发者联盟

存储 知识图谱 检索 图结构 表结构

Amazon Glue 版本 2.0 将作业启动时间缩短了 10 倍,现已全面开放!

亚马逊云科技 (Amazon Web Services)

Amazon Route 53 Resolver 落地中国区,轻松玩转私有域名互访不是梦!| 新服务上线

亚马逊云科技 (Amazon Web Services)

HuskyLens人工智能摄像头

不脱发的程序猿

人工智能 智能硬件 AIOT HuskyLens 人工智能摄像头

再次荣获最受观众喜爱奖

Serverless Devs

阿里云 云原生 cncf #Serverless

官宣:恭喜 ChaosBlade 项目进入 CNCF Sandbox

阿里巴巴云原生

容器 云原生 k8s 监控 Go 语言

Spring Cloud Bus 消息总线介绍

阿里巴巴云原生

Java 微服务 云原生 中间件 数据格式

CampusBulider(模模搭)学习笔记5:创建自定义建筑

ThingJS数字孪生引擎

大前端 可视化 3D 3D可视化 数字孪生

智慧党建三维云展厅可视化

一只数据鲸鱼

数据可视化 智慧党建 三维可视化

数据采集之js自定义采集

大数据技术指南

大数据

源码解析之Seata项目中的分布式ID生成算法

Coder的技术之路

分布式 分布式ID

iMazing比iTunes好用在哪些地方

懒得勤快

限流与Guava RateLimiter原理解析

千珏

Java 微服务 限流算法 Guava 令牌桶

2021年5月国产数据库排行榜:“华为高斯模式”取得成功,阿里OPA持续攀升

墨天轮

数据库 dba tdsql TiDB Gauss DB

新思科技发现开源安全、许可证合规性和维护问题依然很普遍

InfoQ_434670063458

新思科技 OSSRA 开源安全

我崩溃了!BTAJ面试有关散列(哈希)表的面试题详解,电子版已问世

欢喜学安卓

android 程序员 面试 移动开发

STM32电源框图解析(VDD、VSS、VDDA、VSSA、VREF+、VREF-、VBAT等的区别)

不脱发的程序猿

嵌入式 stm32 单片机 电源框图解析

怎么进大厂?166位Java工程师的大厂面试经验分享

北游学Java

Java 面试 大厂

云图说|不要小看不起眼的日志,“小日志,大作用”

华为云开发者联盟

运维 日志 云日志服务 安全监控审计

论好文章和烂文章

阿里巴巴云原生

程序员 开发者 云原生 写作技巧 成长与思考

如何做一场高质量的分享

阿里巴巴云原生

深度学习 开发者 云原生 分享

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