目录:
四、 单元测试下开发模式、技术框架选择
单元测试是按照测试范围来划分的。TDD、BDD 是按照开发模式来划分的。因此就有各种排列组合,这里我们只关心单元测试下的 TDD、BDD 方案。
在单元测试阶段,TDD 和 BDD 都可以适用。
1. TDD
TDD 强调不断的测试推动代码的开发,这样简化了
代码,保证了代码质量。
思想是在拿到一个新的功能时,首先思考该功能如何测试,各种测试用例、各种边界 case;然后完成测试代码的开发;最后编写相应的代码以满足、通过这些测试用例。
TDD 开发过程类似下图:
- 先编写该功能的测试用例,实现测试代码。这时候去跑测试,是不通过的,也就是到了红色的状态
- 然后编写真正的功能实现代码。这时候去跑测试,测试通过,也就是到了绿色的状态
- 在测试用例的保证下,可以重构、优化代码
抛出一个问题:TDD 看上去很好,应该用它吗?
这个问题不用着急回答,回答了也不会有对错之分。开发中经常是这样一个流程,新的需求出来后,先经过技术评审会议,确定宏观层面的技术方案、确定各个端的技术实现、使用的技术等,整理出开发文档、会议文档。工期评估后开始编码。事情这么简单吗?前期即使想的再充分、再细致,可能还是存在特殊 case 漏掉的情况,导致技术方案或者是技术实现的改变。如果采用 TDD,那么之前新功能给到后,就要考虑测试用例的设计、编写了测试代码,在测试用例的保证下再去实现功能。如果遇到了技术方案的变更,之前的测试用例要改变、测试代码实现要改变。可能新增的某个 case 导致大部分的测试代码和实现代码都要改变。
如何开展 TDD**
-
新建一个工程,确保 “Include Unit Tests” 选项是选中的状态
-
创建后的工程目录如下
-
删除 Xcode 创建的测试模版文件
TDDDemoTests.m
-
假如我们需要设计一个人类,它具有吃饭的功能,且当他吃完后会说一句“好饱啊”。
-
那么按照 TDD 我们先设计测试用例。假设有个 Person 类,有个对象方法叫做吃饭,吃完饭后会返回一个“好饱啊”的字符串。那测试用例就是
步骤 期望 结果 实例化 Person 对象,调用对象的 eat 方法 调用后返回“好饱啊” ? -
实现测试用例代码。创建继承自 Unit Test Case class 的测试类,命名为
工程前缀+测试类名+Test
,也就是TDDPersonTest.m
。【自动化交流群】:1140267353 本群提供免费的学习指导,自动化资料以及免费的解答,不懂得问题都可以在本群提出来,之后还会有职业生涯规划以及面试指导; -
因为要测试 Person 类,所以在主工程中创建 Person 类
-
因为要测试人类在吃饭后说一句“好饱啊”。所以设想那个类目前只有一个吃饭的方法。于是在
TDDPersonTest.m
中创建一个测试函数-(void)testReturnStatusStringWhenPersonAte;
函数内容如下- (void)testReturnStatusStringWhenPersonAte { // Given Person *somebody = [[Person alloc] init]; // When NSString *statusMessage = [somebody performSelector:@selector(eat)]; // Then XCTAssert([statusMessage isEqualToString:@"好饱啊"], @"Person 「吃饭后返回“好饱啊”」功能异常"); } 复制代码
-
Xcode 下按快捷键
Command + U
,跑测试代码发现是失败的。因为我们的 Person 类根本没实现相应的方法 -
从 TDD 开发过程可以看到,我们现在是红色的 “Fail” 状态。所以需要去 Person 类中实现功能代码。Person 类如下
#import "Person.h" @implementation Person - (NSString *)eat { [NSThread sleepForTimeInterval:1]; return @"好饱啊";; } @end 复制代码
-
再次运行,跑一下测试用例(Command + U 快捷键)。发现测试通过,也就是TDD 开发过程中的绿色 “Success” 状态。
-
例子比较简单,假如情况需要,可以在
-(void)setUp
方法里面做一些测试的前置准备工作,在-(void)tearDown
方法里做资源释放的操作 -
假如 eat 方法实现的不够漂亮。现在在测试用例的保证下,大胆重构,最后确保所有的 Unit Test case 通过即可。
2. BDD
相比 TDD,BDD 关注的是行为方式的设计,拿上述“人吃饭”举例说明。
和 TDD 相比第1~4步骤相同。
-
BDD 则需要先实现功能代码。创建 Person 类,实现
-(void)eat;
方法。代码和上面的相同 -
BDD 需要引入好用的框架
Kiwi
,使用 Pod 的方式引入 -
因为要测试人类在吃饭后说一句“好饱啊”。所以设想那个类目前只有一个吃饭的方法。于是在
TDDPersonTest.m
中创建一个测试函数-(void)testReturnStatusStringWhenPersonAte;
函数内容如下#import "kiwi.h" #import "Person.h" SPEC_BEGIN(BDDPersonTest) describe(@"Person", ^{ context(@"when someone ate", ^{ it(@"should get a string",^{ Person *someone = [[Person alloc] init]; NSString *statusMessage = [someone eat]; [[statusMessage shouldNot] beNil]; [[statusMessage should] equal:@"好饱啊"]; }); }); }); SPEC_END 复制代码
3. XCTest
开发步骤
Xcode 自带的测试系统是 XCTest
,使用简单。开发步骤如下
-
在
Tests
目录下为被测的类创建一个继承自XCTestCase
的测试类。 -
删除新建的测试代码模版里面的无用方法
- (void)testPerformanceExample
、- (void)testExample
。 -
跟普通类一样,可以继承,可以写私有属性、私有方法。所以可以在新建的类里面,根据需求写一些私有属性等
-
在
- (void)setUp
方法里面写一些初始化、启动设置相关的代码。比如测试数据库功能的时候,写一些数据库连接池相关代码 -
为被测类里面的每个方法写测试方法。被测类里面可能是 n 个方法,测试类里面可能是 m 个方法(m >= n),根据我们在第三部分:单元测试编码规范里讲过的 一个测试用例只测试一个分支,方法内部有 if、switch 语句时,需要为每个分支写测试用例
-
为测试类每个方法写的测试方法有一定的规范。命名必须是
test+被测方法名
。函数无参数、无返回值。比如- (void)testSharedInstance
。 -
测试方法里面的代码按照
Given->When->Then
的顺序展开。测试环境所需的先决条件准备;调用所要测试的某个方法、函数;使用断言验证输出和行为是否符合预期。