TA的每日心情 | 无聊 前天 09:05 |
---|
签到天数: 1050 天 连续签到: 1 天 [LV.10]测试总司令
|
四、 单元测试下开发模式、技术框架选择
单元测试是按照测试范围来划分的。TDD、BDD 是按照开发模式来划分的。因此就有各种排列组合,这里我们只关心单元测试下的 TDD、BDD 方案。
在单元测试阶段,TDD 和 BDD 都可以适用。
1. TDD
TDD 强调不断的测试推动代码的开发,这样简化了代码,保证了代码质量。
思想是在拿到一个新的功能时,首先思考该功能如何测试,各种测试用例、各种边界 case;然后完成测试代码的开发;最后编写相应的代码以满足、通过这些测试用例。
TDD 开发过程类似下图:
先编写该功能的测试用例,实现测试代码。这时候去跑测试,是不通过的,也就是到了红色的状态。
然后编写真正的功能实现代码。这时候去跑测试,测试通过,也就是到了绿色的状态。
在测试用例的保证下,可以重构、优化代码。
抛出一个问题:TDD 看上去很好,应该用它吗?
这个问题不用着急回答,回答了也不会有对错之分。开发中经常是这样一个流程,新的需求出来后,先经过技术评审会议,确定宏观层面的技术方案、确定各个端的技术实现、使用的技术等,整理出开发文档、会议文档。工期评估后开始编码。事情这么简单吗?前期即使想的再充分、再细致,可能还是存在特殊 case 漏掉的情况,导致技术方案或者是技术实现的改变。如果采用 TDD,那么之前新功能给到后,就要考虑测试用例的设计、编写了测试代码,在测试用例的保证下再去实现功能。如果遇到了技术方案的变更,之前的测试用例要改变、测试代码实现要改变。可能新增的某个 case 导致大部分的测试代码和实现代码都要改变。
如何开展 TDD
新建一个工程,确保 “Include Unit Tests” 选项是选中的状态。
创建后的工程目录如下:
删除 Xcode 创建的测试模版文件 TDDDemoTests.m。
假如我们需要设计一个人类,它具有吃饭的功能,且当他吃完后会说一句“好饱啊”。
那么按照 TDD 我们先设计测试用例。假设有个 Person 类,有个对象方法叫做吃饭,吃完饭后会返回一个“好饱啊”的字符串。那测试用例就是:
实现测试用例代码。创建继承自 Unit Test Case class 的测试类,命名为 工程前缀+测试类名+Test,也就是 TDDPersonTest.m。
因为要测试 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_EN
复制代码 3. XCTest
开发步骤
Xcode 自带的测试系统是 XCTest,使用简单。开发步骤如下:
在 Tests 目录下为被测的类创建一个继承自 XCTestCase 的测试类。
删除新建的测试代码模版里面的无用方法 - (void)testPerformanceExample、- (void)testExample。
跟普通类一样,可以继承,可以写私有属性、私有方法。所以可以在新建的类里面,根据需求写一些私有属性等。
在 - (void)setUp 方法里面写一些初始化、启动设置相关的代码。比如测试数据库功能的时候,写一些数据库连接池相关代码。
为被测类里面的每个方法写测试方法。被测类里面可能是 n 个方法,测试类里面可能是 m 个方法(m >= n),根据我们在第三部分:单元测试编码规范里讲过的 一个测试用例只测试一个分支,方法内部有 if、switch 语句时,需要为每个分支写测试用例。
为测试类每个方法写的测试方法有一定的规范。命名必须是 test+被测方法名。函数无参数、无返回值。比如 - (void)testSharedInstance。
测试方法里面的代码按照 Given->When->Then 的顺序展开。测试环境所需的先决条件准备;调用所要测试的某个方法、函数;使用断言验证输出和行为是否符合预期。
在 - (void)tearDown 方法里面写一些释放掉资源或者关闭的代码。比如测试数据库功能的时候,写一些数据库连接池关闭的代码。
断言相关宏:
- /*!
- * [url=home.php?mod=space&uid=10594]@Function[/url] XCTFail(...)
- * Generates a failure unconditionally.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTFail(...) \
- _XCTPrimitiveFail(self, __VA_ARGS__)
- /*!
- * @define XCTAssertNil(expression, ...)
- * Generates a failure when ((\a expression) != nil).
- * @param expression An expression of id type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertNil(expression, ...) \
- _XCTPrimitiveAssertNil(self, expression, @#expression, __VA_ARGS__)
- /*!
- * @define XCTAssertNotNil(expression, ...)
- * Generates a failure when ((\a expression) == nil).
- * @param expression An expression of id type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertNotNil(expression, ...) \
- _XCTPrimitiveAssertNotNil(self, expression, @#expression, __VA_ARGS__)
- /*!
- * @define XCTAssert(expression, ...)
- * Generates a failure when ((\a expression) == false).
- * @param expression An expression of boolean type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssert(expression, ...) \
- _XCTPrimitiveAssertTrue(self, expression, @#expression, __VA_ARGS__)
- /*!
- * @define XCTAssertTrue(expression, ...)
- * Generates a failure when ((\a expression) == false).
- * @param expression An expression of boolean type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertTrue(expression, ...) \
- _XCTPrimitiveAssertTrue(self, expression, @#expression, __VA_ARGS__)
- /*!
- * @define XCTAssertFalse(expression, ...)
- * Generates a failure when ((\a expression) != false).
- * @param expression An expression of boolean type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertFalse(expression, ...) \
- _XCTPrimitiveAssertFalse(self, expression, @#expression, __VA_ARGS__)
- /*!
- * @define XCTAssertEqualObjects(expression1, expression2, ...)
- * Generates a failure when ((\a expression1) not equal to (\a expression2)).
- * @param expression1 An expression of id type.
- * @param expression2 An expression of id type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertEqualObjects(expression1, expression2, ...) \
- _XCTPrimitiveAssertEqualObjects(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
- /*!
- * @define XCTAssertNotEqualObjects(expression1, expression2, ...)
- * Generates a failure when ((\a expression1) equal to (\a expression2)).
- * @param expression1 An expression of id type.
- * @param expression2 An expression of id type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertNotEqualObjects(expression1, expression2, ...) \
- _XCTPrimitiveAssertNotEqualObjects(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
- /*!
- * @define XCTAssertEqual(expression1, expression2, ...)
- * Generates a failure when ((\a expression1) != (\a expression2)).
- * @param expression1 An expression of C scalar type.
- * @param expression2 An expression of C scalar type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertEqual(expression1, expression2, ...) \
- _XCTPrimitiveAssertEqual(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
- /*!
- * @define XCTAssertNotEqual(expression1, expression2, ...)
- * Generates a failure when ((\a expression1) == (\a expression2)).
- * @param expression1 An expression of C scalar type.
- * @param expression2 An expression of C scalar type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertNotEqual(expression1, expression2, ...) \
- _XCTPrimitiveAssertNotEqual(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
- /*!
- * @define XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, ...)
- * Generates a failure when (difference between (\a expression1) and (\a expression2) is > (\a accuracy))).
- * @param expression1 An expression of C scalar type.
- * @param expression2 An expression of C scalar type.
- * @param accuracy An expression of C scalar type describing the maximum difference between \a expression1 and \a expression2 for these values to be considered equal.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, ...) \
- _XCTPrimitiveAssertEqualWithAccuracy(self, expression1, @#expression1, expression2, @#expression2, accuracy, @#accuracy, __VA_ARGS__)
- /*!
- * @define XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, ...)
- * Generates a failure when (difference between (\a expression1) and (\a expression2) is <= (\a accuracy)).
- * @param expression1 An expression of C scalar type.
- * @param expression2 An expression of C scalar type.
- * @param accuracy An expression of C scalar type describing the maximum difference between \a expression1 and \a expression2 for these values to be considered equal.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, ...) \
- _XCTPrimitiveAssertNotEqualWithAccuracy(self, expression1, @#expression1, expression2, @#expression2, accuracy, @#accuracy, __VA_ARGS__)
- /*!
- * @define XCTAssertGreaterThan(expression1, expression2, ...)
- * Generates a failure when ((\a expression1) <= (\a expression2)).
- * @param expression1 An expression of C scalar type.
- * @param expression2 An expression of C scalar type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertGreaterThan(expression1, expression2, ...) \
- _XCTPrimitiveAssertGreaterThan(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
- /*!
- * @define XCTAssertGreaterThanOrEqual(expression1, expression2, ...)
- * Generates a failure when ((\a expression1) < (\a expression2)).
- * @param expression1 An expression of C scalar type.
- * @param expression2 An expression of C scalar type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertGreaterThanOrEqual(expression1, expression2, ...) \
- _XCTPrimitiveAssertGreaterThanOrEqual(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
- /*!
- * @define XCTAssertLessThan(expression1, expression2, ...)
- * Generates a failure when ((\a expression1) >= (\a expression2)).
- * @param expression1 An expression of C scalar type.
- * @param expression2 An expression of C scalar type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertLessThan(expression1, expression2, ...) \
- _XCTPrimitiveAssertLessThan(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
- /*!
- * @define XCTAssertLessThanOrEqual(expression1, expression2, ...)
- * Generates a failure when ((\a expression1) > (\a expression2)).
- * @param expression1 An expression of C scalar type.
- * @param expression2 An expression of C scalar type.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertLessThanOrEqual(expression1, expression2, ...) \
- _XCTPrimitiveAssertLessThanOrEqual(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)
- /*!
- * @define XCTAssertThrows(expression, ...)
- * Generates a failure when ((\a expression) does not throw).
- * @param expression An expression.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertThrows(expression, ...) \
- _XCTPrimitiveAssertThrows(self, expression, @#expression, __VA_ARGS__)
- /*!
- * @define XCTAssertThrowsSpecific(expression, exception_class, ...)
- * Generates a failure when ((\a expression) does not throw \a exception_class).
- * @param expression An expression.
- * @param exception_class The class of the exception. Must be NSException, or a subclass of NSException.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertThrowsSpecific(expression, exception_class, ...) \
- _XCTPrimitiveAssertThrowsSpecific(self, expression, @#expression, exception_class, __VA_ARGS__)
- /*!
- * @define XCTAssertThrowsSpecificNamed(expression, exception_class, exception_name, ...)
- * Generates a failure when ((\a expression) does not throw \a exception_class with \a exception_name).
- * @param expression An expression.
- * @param exception_class The class of the exception. Must be NSException, or a subclass of NSException.
- * @param exception_name The name of the exception.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertThrowsSpecificNamed(expression, exception_class, exception_name, ...) \
- _XCTPrimitiveAssertThrowsSpecificNamed(self, expression, @#expression, exception_class, exception_name, __VA_ARGS__)
- /*!
- * @define XCTAssertNoThrow(expression, ...)
- * Generates a failure when ((\a expression) throws).
- * @param expression An expression.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertNoThrow(expression, ...) \
- _XCTPrimitiveAssertNoThrow(self, expression, @#expression, __VA_ARGS__)
- /*!
- * @define XCTAssertNoThrowSpecific(expression, exception_class, ...)
- * Generates a failure when ((\a expression) throws \a exception_class).
- * @param expression An expression.
- * @param exception_class The class of the exception. Must be NSException, or a subclass of NSException.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertNoThrowSpecific(expression, exception_class, ...) \
- _XCTPrimitiveAssertNoThrowSpecific(self, expression, @#expression, exception_class, __VA_ARGS__)
- /*!
- * @define XCTAssertNoThrowSpecificNamed(expression, exception_class, exception_name, ...)
- * Generates a failure when ((\a expression) throws \a exception_class with \a exception_name).
- * @param expression An expression.
- * @param exception_class The class of the exception. Must be NSException, or a subclass of NSException.
- * @param exception_name The name of the exception.
- * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
- */
- #define XCTAssertNoThrowSpecificNamed(expression, exception_class, exception_name, ...) \
- _XCTPrimitiveAssertNoThrowSpecificNamed(self, expression, @#expression, exception_class, exception_name, __VA_ARGS__)
复制代码 经验小结
XCTestCase 类和其他类一样,你可以定义基类,这里面封装一些常用的方法。
- // HCTTestCase.h
- #import <XCTest/XCTest.h>
- NS_ASSUME_NONNULL_BEGIN
- [url=home.php?mod=space&uid=10061]@interface[/url] HCTTestCase : XCTestCase
- @property (nonatomic, assign) NSTimeInterval networkTimeout;
- /**
- 用一个默认时间设置异步测试 XCTestExpectation 的超时处理
- */
- - (void)waitForExpectationsWithCommonTimeout;
- /**
- 用一个默认时间设置异步测试的
- @param handler 超时的处理逻辑
- */
- - (void)waitForExpectationsWithCommonTimeoutUsingHandler:(XCWaitCompletionHandler __nullable)handler;
- /**
- 生成 Crash 类型的 meta 数据
- [url=home.php?mod=space&uid=26358]@return[/url] meta 类型的字典
- */
- - (NSDictionary *)generateCrashMetaDataFromReport;
- @end
- NS_ASSUME_NONNULL_END
- // HCTTestCase.m
- #import "HCTTestCase.h"
- #import ...
- @implementation HCTTestCase
- #pragma mark - life cycle
- - (void)setUp
- {
- [super setUp];
- self.networkTimeout = 20.0;
- // 1. 设置平台信息
- [self setupAppProfile];
- // 2. 设置 Mget 配置
- [[TITrinityInitManager sharedInstance] setup];
- // ....
- // 3. 设置 HermesClient
- [[HermesClient sharedInstance] setup];
- }
- - (void)tearDown
- {
- [super tearDown];
- }
- #pragma mark - public Method
- - (void)waitForExpectationsWithCommonTimeout
- {
- [self waitForExpectationsWithCommonTimeoutUsingHandler:nil];
- }
- - (void)waitForExpectationsWithCommonTimeoutUsingHandler:(XCWaitCompletionHandler __nullable)handler
- {
- [self waitForExpectationsWithTimeout:self.networkTimeout handler:handler];
- }
- - (NSDictionary *)generateCrashMetaDataFromReport
- {
- NSMutableDictionary *metaDictionary = [NSMutableDictionary dictionary];
- NSDate *crashTime = [NSDate date];
- metaDictionary[@"MONITOR_TYPE"] = @"appCrash";
- // ...
- metaDictionary[@"USER_CRASH_DATE"] = @([crashTime timeIntervalSince1970] * 1000);
- return [metaDictionary copy];
- }
- #pragma mark - private method
- - (void)setupAppProfile
- {
- [[CMAppProfile sharedInstance] setMPlatform:@"70"];
- // ...
- }
- @end
复制代码 上述说的基本是开发规范相关。测试方法内部如果调用了其他类的方法,则在测试方法内部必须 Mock 一个外部对象,限制好返回值等。
在 XCTest 内难以使用 mock 或 stub,这些是测试中非常常见且重要的功能。
例子
这里举个例子,是测试一个数据库操作类 HCTDatabase,代码只放某个方法的测试代码。
- - (void)testRemoveLatestRecordsByCount
- {
- XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库删除最新数据功能"];
- // 1. 先清空数据表
- [dbInstance removeAllLogsInTableType:HCTLogTableTypeMeta];
- // 2. 再插入一批数据
- NSMutableArray *insertModels = [NSMutableArray array];
- NSMutableArray *reportIDS = [NSMutableArray array];
-
- for (NSInteger index = 1; index <= 100; index++) {
- HCTLogMetaModel *model = [[HCTLogMetaModel alloc] init];
- model.log_id = index;
- // ...
- if (index > 90 && index <= 100) {
- [reportIDS addObject:model.report_id];
- }
- [insertModels addObject:model];
- }
- [dbInstance add:insertModels inTableType:HCTLogTableTypeMeta];
-
- // 3. 将早期的数据删除掉(id > 90 && id <= 100)
- [dbInstance removeLatestRecordsByCount:10 inTableType:HCTLogTableTypeMeta];
-
- // 4. 拿到当前的前10条数据和之前存起来的前10条 id 做比较。再判断当前表中的总记录条数是否等于 90
- [dbInstance getLatestRecoreds:10 inTableType:HCTLogTableTypeMeta completion:^(NSArray<HCTLogModel *> * _Nonnull records) {
- NSArray<HCTLogModel *> *latestRTentRecords = records;
-
- [dbInstance getOldestRecoreds:100 inTableType:HCTLogTableTypeMeta completion:^(NSArray<HCTLogModel *> * _Nonnull records) {
- NSArray<HCTLogModel *> *currentRecords = records;
-
- __block BOOL isEarlyData = NO;
- [latestRTentRecords enumerateObjectsUsingBlock:^(HCTLogModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
- if ([reportIDS containsObject:obj.report_id]) {
- isEarlyData = YES;
- }
- }];
-
- XCTAssert(!isEarlyData && currentRecords.count == 90, @"***Database「删除最新n条数据」功能:异常");
- [exception fulfill];
- }];
-
- }];
- [self waitForExpectationsWithCommonTimeout];
- }
复制代码 3. 测试框架
1)Kiwi
BDD 框架里的 Kiwi 可圈可点。使用 CocoaPods 引入 pod 'Kiwi'。看下面的例子。
被测类(Planck 项目是一个基于 WebView 的 SDK,根据业务场景,发现针对 WebView 的大部分功能定制都是基于 WebView 的生命周期内发生的,所以参考 NodeJS 的中间件思想,设计了基于生命周期的 WebView 中间件)。
- #import <Foundation/Foundation.h>
- @interface TPKTrustListHelper : NSObject
- +(void)fetchRemoteTrustList;
- +(BOOL)isHostInTrustlist:(NSString *)scheme;
- +(NSArray *)trustList;
- @end
复制代码 测试类:
- SPEC_BEGIN(TPKTrustListHelperTest)
- describe(@"Middleware Wrapper", ^{
-
- context(@"when get trustlist", ^{
- it(@"should get a array of string",^{
- NSArray *array = [TPKTrustListHelper trustList];
- [[array shouldNot] beNil];
- NSString *first = [array firstObject];
- [[first shouldNot] beNil];
- [[NSStringFromClass([first class]) should] equal:@"__NSCFString"];
- });
- });
-
- context(@"when check a string wether contained in trustlist ", ^{
- it(@"first string should contained in trustlist",^{
- NSArray *array = [TPKTrustListHelper trustList];
- NSString *first = [array firstObject];
- [[theValue([TPKTrustListHelper isHostInTrustlist:first]) should] equal:@(YES)];
- });
- });
- });
- SPEC_END
复制代码 例子包含 Kiwi 的最基础元素。SPEC_BEGIN 和 SPEC_END 表示测试类;describe 描述需要被测试的类;context 表示一个测试场景,也就是 Given->When->Then 里的 Given;it 表示要测试的内容,也就是也就是 Given->When->Then 里的 When 和 Then。1个 describe 下可以包含多个 context,1个 context 下可以包含多个 it。
Kiwi 的使用分为:Specs、 Expectations 、 Mocks and Stubs 、Asynchronous Testing 四部分。
it 里面的代码块是真正的测试代码,使用链式调用的方式,简单上手。
测试领域中 Mock 和 Stub 非常重要。Mock 模拟对象可以降低对象之间的依赖,模拟出一个纯净的测试环境(类似初中物理课上“控制变量法”的思想)。Kiwi 也支持的非常好,可以模拟对象、模拟空对象、模拟遵循协议的对象等等,点击 Mocks and Stubs 查看。Stub 存根可以控制某个方法的返回值,这对于方法内调用别的对象的方法返回值很有帮助。减少对于外部的依赖,单一测试当前行为是否符合预期。
针对异步测试,XCTest 则需要创建一个 XCTestExpectation 对象,在异步实现里面调用该对象的 fulfill 方法,最后设置最大等待时间和完成的回调 - (void)waitForExpectationsWithTimeoutNSTimeInterval)timeout handlernullable XCWaitCompletionHandler)handler; 如下例子:
- XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库插入功能"];
- [dbInstance removeAllLogsInTableType:HCTLogTableTypeMeta];
- NSMutableArray *insertModels = [NSMutableArray array];
- for (NSInteger index = 1; index <= 10000; index++) {
- HCTLogMetaModel *model = [[HCTLogMetaModel alloc] init];
- model.log_id = index;
- // 。。。
- [insertModels addObject:model];
- }
- [dbInstance add:insertModels inTableType:HCTLogTableTypeMeta];
- [dbInstance recordsCountInTableType:HCTLogTableTypeMeta completion:^(NSInteger count) {
- XCTAssert(count == insertModels.count, @"**Database「数据增加」功能:异常");
- [exception fulfill];
- }];
- [self waitForExpectationsWithCommonTimeout];
复制代码 2)expecta、Specta
expecta 和 Specta 都出自 orta 之手,他也是 Cocoapods 的开发者之一。太牛逼了,工程化、质量保证领域的大佬。
Specta 是一个轻量级的 BDD 测试框架,采用 DSL 模式,让测试更接近于自然语言,因此更易读。
特点:
易于集成到项目中。在 Xcode 中勾选 Include Unit Tests ,和 XCTest 搭配使用。
语法很规范,对比 Kiwi 和 Specta 的文档,发现很多东西都是相同的,也就是很规范,所以学习成本低、后期迁移到其他框架很平滑。
Expecta 是一个匹配(断言)框架,相比 Xcode 的断言 XCAssert,Excepta 提供更加丰富的断言。
特点:
Eepecta 没有数据类型限制,比如 1,并不关心是 NSInteger 还是 CGFloat链式编程,写起来很舒服。
反向匹配,很灵活。断言匹配用 except(...).to.equal(...),断言不匹配则使用 .notTo 或者 .toNot
延时匹配,可以在链式表达式后加入 .will、.willNot、.after(interval) 等。
4.小结
Xcode 自带的 XCTestCase 比较适合 TDD,不影响源代码,系统独立且不影响 App 包大小。适合简单场景下的测试。且每个函数在最左侧又个测试按钮,点击后可以单独测试某个函数。
Kiwi 是一个强大的 BDD 框架,适合稍微复杂写的项目,写法舒服、功能强大,模拟对象、存根语法、异步测试等满足几乎所有的测试场景。不能和 XCTest 继承。
Specta 也是一个 BDD 框架,基于 XCTest 开发,可以和 XCTest 模版集合使用。相比 Kiwi,Specta 轻量一些。开发中一般搭配 Excepta 使用。如果需要使用 Mock 和 Stud 可以搭配 OCMock。
Excepta 是一个匹配框架,比 XCTest 的断言则更加全面一些。
没办法说哪个最好、最合理,根据项目需求选择合适的组合。
|
|