iOS单元测试

1、什么是单元测试

单元测试又称为模块测试,是指对软件中的最小可测试单元进行检查和验证。是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。

1、单元测试的本质

  1. 是一种验证行为

    单元测试在开发前期检验了代码逻辑的正确性,开发后期,无论是修改代码内部抑或重构,测试的结果为这一切提供了可量化的保障。

  2. 是一种设计行为

    为了可进行单元测试,尤其是先写单元测试(TDD),我们将从调用者思考,从接口上思考,我们必须把程序单元设计成接口功能划分清晰的,易于测试的,且与外部模块耦合性尽可能小。

  3. 是一种快速回归的方式

    在原代码基础上开发及修改功能时,单元测试是一种快捷,可靠的回归。

  4. 是程序优良的文档

    从效果上而言,单元测试就像是能执行的文档,说明了在你用各种条件调用代码时,你所能期望这段代码完成的功能。

2、在什么时候我们需要单元测试

  1. 当发现自己改了代码之后,经常需要手动验证的时候
  2. 改了代码之后, 怕影响其他地方的bug
  3. 多人合作项目,可能交给其他人不放心别人改你的代码的时候

以上都可以通过写测试案例,进行自动化测试,从而减少bug。

3、单元测试的目的

  1. 保证代码的质量

    代码可以通过编译器检查语法的正确性,却不能保证代码逻辑是正确的,尤其包含了许多单元分支的情况下,单元测试可以保证代码的行为和结果与我们的预期和需求一致。在测试某段代码的行为是否和你的期望一致时,你需要确认,在任何情况下,这段代码是否都和你的期望一致,譬如参数可能为空,可能的异步操作等。

  2. 保证代码的可维护性

    保证原有单元测试正确的情况下,无论如何修改单元内部代码,测试的结果应该是正确的,且修改后不会影响到其他的模块。

  3. 保证代码的可扩展性

    为了保证可行的可持续的单元测试,程序单元应该是低耦合的,否则,单元测试将难以进行。

2、单元测试的两种设计思想

1、 测试驱动开发 Test Driven Development(TDD)

先写测试程序,然后编码实现其功能。

测试驱动开发是戴两顶帽子思考的开发方式:先戴上实现功能的帽子,在测试的辅助下,快速实现其功能;再戴上重构的帽子,在测试的保护下,通过去除冗余的代码,提高代码质量。

2、 行为驱动开发 Behavior Driven Development(BDD)

它通过用自然语言书写非程序员可读的测试用例扩展了 测试驱动开发方法(TDD)。这让开发者得以把精力集中在代码应该怎么写,而不是技术细节上,而且也最大程度的减少了将代码编写者的技术语言与商业客户、用户、利益相关者、项目管理者等的领域语言之间来回翻译的代价。

例如现在比较流行的框架kiwi就是行为驱动开发。下文会简单展示kiwi的使用和设计思想。

3、使用Xcode编写第一个测试案例。

demo下载

1、 创建单元测试Target

image

如上图所视,在创建项目的时候就可以选择是否添加单元测试。如果创建项目的时候没有添加,也可以通过
file - new - target - Test - Unit Testing bundle 来创建

2、 单元测试类介绍

image

如上图所示,其实Xcode已经说的很清楚了。

  • setUp 用于做一些初始化代码
  • TearDown 用于对象的销毁
  • 所有测试类都必须继承XCTestCase
  • 测试方法必须以testXXX开头,Xcode会自动识别出所有的测试方法
  • 在一个类中测试方法的调用顺序是按照方法的顺序来调用的

调用

  • 执行所有测试方法:command + u
  • 只执行某个测试方法:点击方法前的菱形(目前是对号/错号)
  • 执行某个类的所有测试方法:点击类前的菱形(目前是对号/错号)

3、写案例测试

我们在viewController里面添加一个方法计算工资税。如图所示


image

我们想验证这个方法是否计算正确,便可以创建测试来测试。如下图所示

image

点每一个方法的左边的菱形图标就会单独测试。如果点类名旁边的菱形图标便会全类测试,执行方法按顺序执行。

如上图所示,第二第三个方法验证通过,第一个并没有通过。是我们通过XCTAssert为我们提供的系统断言 XSTAsserTrue 进行的判断,那么除了 XSTAsserTrue
还有那些可以用于判断的呢。下面简单介绍常用的

4、常用断言

- (void)testExample {
    NSLog(@"--- 失败之前的输出");
    XCTFail(@"无论怎么样都是报错");
    NSLog(@"--- 失败之后的输出");
}

- (void)testNil {
//    XCTAssertNil(expression, ...)
//    expression为空时通过,否则测试失败。
//    expression接受id类型的参数。
    
//    XCTAssertNotNil(expression, ...)
//    expression不为空时通过,否则测试失败。
    
    NSString *name = @"小明";
//    XCTAssertNil(name, @"报错信息输出");
//    XCTAssertNotNil(name, @"viewcontroller 是 nil ");
}

- (void)testTrue {
//    XCTAssert(expression, ...)
//    expression为true时通过,否则测试失败。
//    expression接受boolean类型的参数。

//    XCTAssertTrue(expression, ...)
//    expression为true时通过,否则测试失败。
//    expression接受boolean类型的参数。
    
//    XCTAssertFalse(expression, ...)
//    expression为false时通过,否则测试失败。
//    expression接受boolean类型的参数。
    
    NSInteger number = 10;
//    XCTAssert(number == 10, @"报错信息输出");
//    XCTAssertTrue(number == 10, @"报错信息输出");
    XCTAssertFalse(number != 10, @"报错信息输出");
}

- (void)testEqual {
//    XCTAssertEqualObjects(expression1, expression2, ...)
//    expression1和expression1地址相同时通过,否则测试失败。
//    expression接受id类型的参数。
//
//    XCTAssertNotEqualObjects(expression1, expression2, ...)
//    expression1和expression1地址不相同时通过,否则测试失败。
//    expression接受id类型的参数。
//
//    XCTAssertEqual(expression1, expression2, ...)
//    expression1和expression1相等时通过,否则测试失败。
//    expression接受基本类型的参数(数值、结构体之类的)。
//
//    XCTAssertNotEqual(expression1, expression2, ...)
//    expression1和expression1不相等时通过,否则测试失败。
//    expression接受基本类型的参数。
 
//    NSString *name = @"小明";
//    NSString *name2 = name;
//    NSString *name3 = @"小红";
//
//    XCTAssertEqualObjects(name, name2, @"name1 不等于 name 2");
//    XCTAssertEqualObjects(name, @"小明", @"name1 不等于 小明");
//    XCTAssertEqualObjects(name, name3, @"name1 不等于 name3");
    
    NSInteger number1 = 10;
    NSInteger number2 = 12;
    
//    XCTAssertEqual(number1, number2, @"number1 不等于 number 2");
    XCTAssertNotEqual(number1, number2, @"number1 等于 number 2");
}

点击可查阅更多断言

5、异步测试

XCTestExpectation 是系统为我们提供的异步测试的API,可以帮助我们测试接口,响应速度等。

image

如图 我们创建分线程来计算工资税

测试方法,如下图

  1. 创建XCTestExpectation对象
  2. 设置等待时间
    [self waitForExpectations:@[exp] timeout:0.5];
  3. 在计算完成时调用 fulfill
    如果在规定时间内没有调用就算超时会报错。
image

然后测试他执行100万次,需要时间是否大于0.5秒。
显然不可以。(实测 1 秒多)

6、耦合测试

在现实项目中我们需要测试的案例肯定比以上所列举内容要复杂。例如下举例说明:如图我们有一个计算 班级有多少人英语成绩达到优秀的算法

image

这个方法又是依赖与另一个工具类如下。

image

如果我们直接测试 优秀英语学生数量的方法,是不行的。因为我们创建的测试对象 他并没有为 tool 初始化。如图所示:

image

那我们怎么解决这个问题呢。这个时候我们就需要 mock 和 stub 。简单理解

  • Mock 泛指模拟的类
  • Stub 泛指模拟类的方法

那我们直接使用XCTest Mock 不就好了吗,结果是失望的,他并没有为我们提供Mock功能。 所以就需要我们去选择一些适合的第三方。

下文会举案例介绍,并提供demo。

4、单元测试框架选择

1、行为驱动开发(BDD) 和 Kiwi框架 介绍

demo下载

KiwiBDD所说做到了 通过用自然语言书写非程序员可读的测试用例
例: 如图我们有一个Student类,并需要测试。

image

使用kiwi测试代码如下


image

内容很容易理解,就和在讲故事一样。

  1. 在所有开始之前我们需要一个stu对象
  2. 在所有之后我们需要销毁测试对象
  3. 这个学生对象应该有名字
  4. 这个学生对象应该有所有科目有分数
  5. 他应该有总分,并且计算正确

这样一个流程,就是一个完成的测试流程。在测试过程中如果哪个环节出错了,一目了然。

2、测试驱动开发(TDD) XCTest+OCMock

demo下载

测试驱动思想在第一章已经介绍过了,在这里就直接说案例了。

因为XCTestXcode深度集成,这也是很多人选择TDD,避免第三方的集成。在有需要的情况下在集成OCMock

在第三章第六节预留了一个问题。使用Mock和Stub解决测试 优秀英语学生数量的方法的问题。如下图当我们集成过OCMock之后

image

和之前对比,我们做的操作基本可以列成三部

  1. Mock一个Student对象
  2. Mock一个StudentTool对象,并验证
  3. 帮Mock的StudentTool置换给Studenttool并验证

这样我们便可以完成优秀英语学生数量方法的测试。

由上我们可以看出一个问题,有依赖的方法是不方便与测试的。所以说单元测试也是一种设计行为,为了可以让代码得到优质的测试环境,我们就会去写一个优质,低耦合,逻辑清晰的代码。这也是TDD的意义所在。

3、方案对比

由上文所说,似乎各有优点,使用类似Kiwi框架的BDD一个是以讲故事的形式来写测试用例。使用XCTest + OCMockTDD可以让程序员写出更好的代码。那么他们是否有自的缺点呢。如下图所示:

image

显而易见,TDD 更占优势。在我们不集成第三方的情况XCTest也能供我们编写测试案例。
并且当你尝试去写Kiwi的时候,你会发现Kiwi在未完全执行所有测试用例时,是无法看到单个测试方法的,更无法执行单个测试。Kiwi的最小测试单位为一个测试用例类,而XCTest的最小测试单位为测试用例类的一个测试方法。

那么既然XCTest+OCMock这么好,别的第三方在写测试用例的时候也都是这么选择的吗 ? 如图

image

总结我认为 XCTest+OCMock是更好的选择。


参考文献:

XCTest apple 文档

单元测试入门和配置

iOS单元测试初探以及OCMock使用入门

单元测试和OCMock

kiwi 实践

Kiwi 进阶

单元测试实战经验

单元测试框架选型

更新中···
武汉加油,中国加油。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容