mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
docs: 汇编研究
This commit is contained in:
@@ -2,8 +2,6 @@
|
||||
|
||||
> 相信在国内一些中小型公司,开发者很少会去写软件测试相关的代码。当然这背后有一些原因在。本文就讲讲 iOS 开发中的软件测试相关的内容。
|
||||
|
||||
|
||||
|
||||
## 一、 测试的重要性
|
||||
|
||||
测试很重要!测试很重要!测试很重要!重要的事情说三遍。
|
||||
@@ -16,10 +14,6 @@
|
||||
|
||||
相信大多数开发者都遇到过上述场景的问题。其实上述这几个问题都有解,那就是“软件测试”。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 二、软件测试
|
||||
|
||||
### 1. 分类
|
||||
@@ -28,8 +22,6 @@
|
||||
|
||||
合理应用软件测试技术,就可以规避掉第一部分的3个场景下的问题。
|
||||
|
||||
|
||||
|
||||
软件测试强调开发、测试同步进行,甚至是测试先行,从需求评审阶段就先考虑好软件测试方案,随后才进行技术方案评审、开发编码、单元测试、集成测试、系统测试、回归测试、验收测试等。
|
||||
|
||||
软件测试从测试范围分为:单元测试、集成测试、系统测试、回归测试、验收测试(有些公司会谈到“冒烟测试“,这个词的精确定义不知道,但是学软件测试课的时候按照范围就只有上述几个分类)。工程师自己负责的是单元测试。测试工程师、QA 负责的是集成测试、系统测试。
|
||||
@@ -38,8 +30,6 @@
|
||||
|
||||
软件测试从开发模式分为:面向测试驱动开发 TDD (Test-driven development)、面向行为驱动开发 BDD (Behavior-driven development)。
|
||||
|
||||
|
||||
|
||||
### 2. TDD
|
||||
|
||||
TDD 的思想是:先编写测试用例,再快速开发代码,然后在测试用例的保证下,可以方便安全地进行代码重构,提升应用程序的质量。一言以蔽之就是通过测试来推动开发的进行。正是由于这个特点,TDD 被广泛使用于敏捷开发。
|
||||
@@ -50,8 +40,6 @@ TDD 的思想是:先编写测试用例,再快速开发代码,然后在测
|
||||
|
||||
缺点:技术方案需要先评审结束、架构需要提前搭建好。假如需求变动,则前面步骤需要重新执行,灵活性较差。
|
||||
|
||||
|
||||
|
||||
### 3. BDD
|
||||
|
||||
BDD 即行为驱动开发,是敏捷开发**技术**之一,通过自然语言定义系统行为,以功能使用者的角度,编写需求场景,且这些行为描述可以直接形成需求文档,同时也是测试标准。
|
||||
@@ -60,22 +48,14 @@ BDD 的思想是跳出单一的函数,针对的是行为而展开的测试。B
|
||||
|
||||
优点:各团队的成员可以集中在一起,设计基于行为的计测试用例。
|
||||
|
||||
|
||||
|
||||
### 4. 对比
|
||||
|
||||
根据特点也就是找到了各自的使用场景,TDD 主要针对开发中的最小单元进行测试,适合单元测试。而 BDD 针对的是行为,所以测试范围可以再大一些,在集成测试、系统测试中都可以使用
|
||||
|
||||
|
||||
|
||||
TDD 编写的测试用例一般针对的是开发中的最小单元(比如某个类、函数、方法)而展开,适合单元测试。
|
||||
|
||||
BDD 编写的测试用例针对的是行为,测试范围更大一些,适合集成测试、系统测试阶段。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 三、 单元测试编码规范<a name="codeRules"></a>
|
||||
|
||||
本文的主要重点是针对日常开发阶段工程师可以做的事情,也就是单元测试而展开。
|
||||
@@ -98,19 +78,18 @@ BDD 编写的测试用例针对的是行为,测试范围更大一些,适合
|
||||
for (NSInteger index = 1; index <= 10000; index++) {
|
||||
HCTLogMetaModel *model = [[HCTLogMetaModel alloc] init];
|
||||
model.log_id = index;
|
||||
// ...
|
||||
// ...
|
||||
[insertModels addObject:model];
|
||||
}
|
||||
// when
|
||||
[dbInstance add:insertModels inTableType:HCTLogTableTypeMeta];
|
||||
// then
|
||||
[dbInstance recordsCountInTableType:HCTLogTableTypeMeta completion:^(NSInteger count) {
|
||||
// then
|
||||
[dbInstance recordsCountInTableType:HCTLogTableTypeMeta completion:^(NSInteger count) {
|
||||
XCTAssert(count == insertModels.count, @"「数据增加」功能:异常");
|
||||
[exception fulfill];
|
||||
}];
|
||||
[self waitForExpectationsWithCommonTimeout];
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
可以看到这个方法的名称为 testInsertDataInOneSpecifiedTable,这段代码做的事情通过函数名可以看出来:测试插入数据到某个特定的表。这个测试用例分为3部分:测试环境所需的先决条件准备;调用所要测试的某个方法、函数;验证输出和行为是否符合预期。
|
||||
@@ -119,8 +98,6 @@ BDD 编写的测试用例针对的是行为,测试范围更大一些,适合
|
||||
|
||||
所以单元测试的代码规范也就出来了。此外单元测试代码规范统一后,每个人的测试代码都按照这个标准展开,那其他人的阅读起来就更加容易、方便。按照这3个步骤去阅读、理解测试代码,就可以清晰明了的知道在做什么。
|
||||
|
||||
|
||||
|
||||
### 2. 一个测试用例只测试一个分支
|
||||
|
||||
我们写的代码有很多语句组成,有各种逻辑判断、分支(if...else、swicth)等等,因此一个程序从一个单一入口进去,过程可能产生 n 个不同的分支,但是程序的出口总是一个。所以由于这样的特性,我们的测试也需要针对这样的现状走完尽可能多的分支。相应的指标叫做「分支覆盖率」。
|
||||
@@ -153,8 +130,6 @@ BDD 编写的测试用例针对的是行为,测试范围更大一些,适合
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 3. 明确标识被测试类
|
||||
|
||||
这条主要站在团队合作和代码可读性角度出发来说明。写过单元测试的人都知道,可能某个函数本来就10行代码,可是为了测试它,测试代码写了30行。一个方法这样写问题不大,多看看就看明白是在测试哪个类的哪个方法。可是当这个类本身就很大,测试代码很大的情况下,不管是作者自身还是多年后负责维护的其他同事,看这个代码阅读成本会很大,需要先看测试文件名 `代码类名 + Test` 才知道是测试的是哪个类,看测试方法名 `test + 方法名` 才知道是测试的是哪个方法。
|
||||
@@ -201,8 +176,6 @@ BDD 编写的测试用例针对的是行为,测试范围更大一些,适合
|
||||
@end
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 4. 使用分类来暴露私有方法、私有变量
|
||||
|
||||
某些场景下写的测试方法内部可能需要调用被测对象的私有方法,也可能需要访问被测对象的某个私有属性。但是测试类里面是访问不到被测类的私有属性和私有方法的,借助于 `Category` 可以实现这样的需求。
|
||||
@@ -221,7 +194,7 @@ BDD 编写的测试用例针对的是行为,测试范围更大一些,适合
|
||||
- (void)hello;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation HermesClientTest
|
||||
|
||||
- (void)testPrivatePropertyAndMethod
|
||||
@@ -232,8 +205,6 @@ BDD 编写的测试用例针对的是行为,测试范围更大一些,适合
|
||||
@end
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 四、 单元测试下开发模式、技术框架选择
|
||||
|
||||
单元测试是按照测试范围来划分的。TDD、BDD 是按照开发模式来划分的。因此就有各种排列组合,这里我们只关心单元测试下的 TDD、BDD 方案。
|
||||
@@ -254,22 +225,18 @@ TDD 开发过程类似下图:
|
||||
- 然后编写真正的功能实现代码。这时候去跑测试,测试通过,也就是到了绿色的状态
|
||||
- 在测试用例的保证下,可以重构、优化代码
|
||||
|
||||
|
||||
|
||||
**抛出一个问题:TDD 看上去很好,应该用它吗?**
|
||||
|
||||
这个问题不用着急回答,回答了也不会有对错之分。开发中经常是这样一个流程,新的需求出来后,先经过技术评审会议,确定宏观层面的技术方案、确定各个端的技术实现、使用的技术等,整理出开发文档、会议文档。工期评估后开始编码。事情这么简单吗?前期即使想的再充分、再细致,可能还是存在特殊 case 漏掉的情况,导致技术方案或者是技术实现的改变。如果采用 TDD,那么之前新功能给到后,就要考虑测试用例的设计、编写了测试代码,在测试用例的保证下再去实现功能。如果遇到了技术方案的变更,之前的测试用例要改变、测试代码实现要改变。可能新增的某个 case 导致大部分的测试代码和实现代码都要改变。
|
||||
|
||||
|
||||
|
||||
如何开展 TDD**
|
||||
|
||||
1. 新建一个工程,确保 “Include Unit Tests” 选项是选中的状态
|
||||
|
||||
|
||||

|
||||
|
||||
2. 创建后的工程目录如下
|
||||
|
||||
|
||||

|
||||
|
||||
3. 删除 Xcode 创建的测试模版文件 `TDDDemoTests.m`
|
||||
@@ -277,28 +244,28 @@ TDD 开发过程类似下图:
|
||||
4. 假如我们需要设计一个人类,它具有吃饭的功能,且当他吃完后会说一句“好饱啊”。
|
||||
|
||||
5. 那么按照 TDD 我们先设计测试用例。假设有个 Person 类,有个对象方法叫做吃饭,吃完饭后会返回一个“好饱啊”的字符串。那测试用例就是
|
||||
|
||||
| 步骤 | 期望 | 结果 |
|
||||
| --------------------------------------- | ------------------ | ---- |
|
||||
|
||||
| 步骤 | 期望 | 结果 |
|
||||
| -------------------------- | ---------- | --- |
|
||||
| 实例化 Person 对象,调用对象的 eat 方法 | 调用后返回“好饱啊” | ? |
|
||||
|
||||
6. 实现测试用例代码。创建继承自 Unit Test Case class 的测试类,命名为 `工程前缀+测试类名+Test`,也就是 `TDDPersonTest.m`。
|
||||
|
||||
|
||||

|
||||
|
||||
7. 因为要测试 Person 类,所以在主工程中创建 Person 类
|
||||
|
||||
8. 因为要测试人类在吃饭后说一句“好饱啊”。所以设想那个类目前只有一个吃饭的方法。于是在 `TDDPersonTest.m` 中创建一个测试函数 `-(void)testReturnStatusStringWhenPersonAte;`函数内容如下
|
||||
|
||||
|
||||
```objective-c
|
||||
- (void)testReturnStatusStringWhenPersonAte
|
||||
{
|
||||
// Given
|
||||
Person *somebody = [[Person alloc] init];
|
||||
|
||||
|
||||
// When
|
||||
NSString *statusMessage = [somebody performSelector:@selector(eat)];
|
||||
|
||||
|
||||
// Then
|
||||
XCTAssert([statusMessage isEqualToString:@"好饱啊"], @"Person 「吃饭后返回“好饱啊”」功能异常");
|
||||
}
|
||||
@@ -307,7 +274,7 @@ TDD 开发过程类似下图:
|
||||
9. Xcode 下按快捷键 `Command + U`,跑测试代码发现是失败的。因为我们的 Person 类根本没实现相应的方法
|
||||
|
||||
10. 从 [TDD 开发过程](#TDDStructure)可以看到,我们现在是红色的 “Fail” 状态。所以需要去 Person 类中实现功能代码。Person 类如下
|
||||
|
||||
|
||||
```objective-c
|
||||
#import "Person.h"
|
||||
|
||||
@@ -328,8 +295,6 @@ TDD 开发过程类似下图:
|
||||
|
||||
13. 假如 eat 方法实现的不够漂亮。现在在测试用例的保证下,大胆重构,最后确保所有的 Unit Test case 通过即可。
|
||||
|
||||
|
||||
|
||||
### 2. BDD
|
||||
|
||||
相比 TDD,BDD 关注的是行为方式的设计,拿上述“人吃饭”举例说明。
|
||||
@@ -341,7 +306,7 @@ TDD 开发过程类似下图:
|
||||
6. BDD 需要引入好用的框架 `Kiwi`,使用 Pod 的方式引入
|
||||
|
||||
7. 因为要测试人类在吃饭后说一句“好饱啊”。所以设想那个类目前只有一个吃饭的方法。于是在 `TDDPersonTest.m` 中创建一个测试函数 `-(void)testReturnStatusStringWhenPersonAte;`函数内容如下
|
||||
|
||||
|
||||
```objective-c
|
||||
#import "kiwi.h"
|
||||
#import "Person.h"
|
||||
@@ -351,7 +316,7 @@ TDD 开发过程类似下图:
|
||||
describe(@"Person", ^{
|
||||
context(@"when someone ate", ^{
|
||||
it(@"should get a string",^{
|
||||
Person *someone = [[Person alloc] init];
|
||||
Person *someone = [[Person alloc] init];
|
||||
NSString *statusMessage = [someone eat];
|
||||
[[statusMessage shouldNot] beNil];
|
||||
[[statusMessage should] equal:@"好饱啊"];
|
||||
@@ -362,8 +327,6 @@ TDD 开发过程类似下图:
|
||||
SPEC_END
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 3. XCTest
|
||||
|
||||
**开发步骤**
|
||||
@@ -386,8 +349,6 @@ Xcode 自带的测试系统是 `XCTest`,使用简单。开发步骤如下
|
||||
|
||||
- 在 `- (void)tearDown` 方法里面写一些释放掉资源或者关闭的代码。比如测试数据库功能的时候,写一些数据库连接池关闭的代码
|
||||
|
||||
|
||||
|
||||
**断言相关宏**
|
||||
|
||||
```c++
|
||||
@@ -607,12 +568,10 @@ Xcode 自带的测试系统是 `XCTest`,使用简单。开发步骤如下
|
||||
_XCTPrimitiveAssertNoThrowSpecificNamed(self, expression, @#expression, exception_class, exception_name, __VA_ARGS__)
|
||||
```
|
||||
|
||||
|
||||
|
||||
**经验小结**
|
||||
|
||||
1. XCTestCase 类和其他类一样,你可以定义基类,这里面封装一些常用的方法。
|
||||
|
||||
|
||||
```
|
||||
// HCTTestCase.h
|
||||
#import <XCTest/XCTest.h>
|
||||
@@ -622,100 +581,98 @@ Xcode 自带的测试系统是 `XCTest`,使用简单。开发步骤如下
|
||||
@interface HCTTestCase : XCTestCase
|
||||
|
||||
@property (nonatomic, assign) NSTimeInterval networkTimeout;
|
||||
|
||||
```
|
||||
|
||||
/**
|
||||
用一个默认时间设置异步测试 XCTestExpectation 的超时处理
|
||||
*/
|
||||
- (void)waitForExpectationsWithCommonTimeout;
|
||||
|
||||
/**
|
||||
用一个默认时间设置异步测试的
|
||||
|
||||
@param handler 超时的处理逻辑
|
||||
*/
|
||||
- (void)waitForExpectationsWithCommonTimeoutUsingHandler:(XCWaitCompletionHandler __nullable)handler;
|
||||
|
||||
|
||||
- (void)waitForExpectationsWithCommonTimeout;
|
||||
|
||||
/**
|
||||
用一个默认时间设置异步测试的
|
||||
|
||||
@param handler 超时的处理逻辑
|
||||
*/
|
||||
|
||||
- (void)waitForExpectationsWithCommonTimeoutUsingHandler:(XCWaitCompletionHandler __nullable)handler;
|
||||
|
||||
/**
|
||||
生成 Crash 类型的 meta 数据
|
||||
|
||||
|
||||
@return 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
|
||||
```
|
||||
|
||||
- (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
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
2. 上述说的基本是开发规范相关。测试方法内部如果调用了其他类的方法,则在测试方法内部必须 Mock 一个外部对象,限制好返回值等。
|
||||
|
||||
3. 在 XCTest 内难以使用 mock 或 stub,这些是测试中非常常见且重要的功能
|
||||
|
||||
|
||||
|
||||
**例子**
|
||||
|
||||
这里举个例子,是测试一个数据库操作类 `HCTDatabase`,代码只放某个方法的测试代码。
|
||||
@@ -729,7 +686,7 @@ Xcode 自带的测试系统是 `XCTest`,使用简单。开发步骤如下
|
||||
// 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;
|
||||
@@ -740,37 +697,33 @@ Xcode 自带的测试系统是 `XCTest`,使用简单。开发步骤如下
|
||||
[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
|
||||
@@ -791,7 +744,6 @@ BDD 框架里的 Kiwi 可圈可点。使用 CocoaPods 引入 `pod 'Kiwi'`。看
|
||||
+(NSArray *)trustList;
|
||||
|
||||
@end
|
||||
|
||||
```
|
||||
|
||||
测试类
|
||||
@@ -799,7 +751,7 @@ BDD 框架里的 Kiwi 可圈可点。使用 CocoaPods 引入 `pod 'Kiwi'`。看
|
||||
```objective-c
|
||||
SPEC_BEGIN(TPKTrustListHelperTest)
|
||||
describe(@"Middleware Wrapper", ^{
|
||||
|
||||
|
||||
context(@"when get trustlist", ^{
|
||||
it(@"should get a array of string",^{
|
||||
NSArray *array = [TPKTrustListHelper trustList];
|
||||
@@ -809,7 +761,7 @@ describe(@"Middleware Wrapper", ^{
|
||||
[[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];
|
||||
@@ -838,7 +790,7 @@ XCTestExpectation *exception = [self expectationWithDescription:@"测试数据
|
||||
for (NSInteger index = 1; index <= 10000; index++) {
|
||||
HCTLogMetaModel *model = [[HCTLogMetaModel alloc] init];
|
||||
model.log_id = index;
|
||||
// 。。。
|
||||
// 。。。
|
||||
[insertModels addObject:model];
|
||||
}
|
||||
[dbInstance add:insertModels inTableType:HCTLogTableTypeMeta];
|
||||
@@ -849,10 +801,6 @@ XCTestExpectation *exception = [self expectationWithDescription:@"测试数据
|
||||
[self waitForExpectationsWithCommonTimeout];
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#### 2. expecta、Specta
|
||||
|
||||
expecta 和 Specta 都出自 [orta](https://github.com/orta) 之手,他也是 Cocoapods 的开发者之一。太牛逼了,工程化、质量保证领域的大佬。
|
||||
@@ -864,8 +812,6 @@ Specta 是一个轻量级的 BDD 测试框架,采用 DSL 模式,让测试更
|
||||
- 易于集成到项目中。在 Xcode 中勾选 `Include Unit Tests` ,和 XCTest 搭配使用
|
||||
- 语法很规范,对比 Kiwi 和 Specta 的文档,发现很多东西都是相同的,也就是很规范,所以学习成本低、后期迁移到其他框架很平滑。
|
||||
|
||||
|
||||
|
||||
Expecta 是一个匹配(断言)框架,相比 Xcode 的断言 XCAssert,Excepta 提供更加丰富的断言。
|
||||
|
||||
特点:
|
||||
@@ -875,8 +821,6 @@ Expecta 是一个匹配(断言)框架,相比 Xcode 的断言 XCAssert,Ex
|
||||
- 反向匹配,很灵活。断言匹配用 `except(...).to.equal(...)`,断言不匹配则使用 `.notTo` 或者 `.toNot`
|
||||
- 延时匹配,可以在链式表达式后加入 `.will`、`.willNot`、`.after(interval)` 等
|
||||
|
||||
|
||||
|
||||
### 4. 小结
|
||||
|
||||
Xcode 自带的 `XCTestCase` 比较适合 TDD,不影响源代码,系统独立且不影响 App 包大小。适合简单场景下的测试。且每个函数在最左侧又个测试按钮,点击后可以单独测试某个函数。
|
||||
@@ -889,8 +833,6 @@ Excepta 是一个匹配框架,比 XCTest 的断言则更加全面一些。
|
||||
|
||||
没办法说哪个最好、最合理,根据项目需求选择合适的组合。
|
||||
|
||||
|
||||
|
||||
## 五、网络测试
|
||||
|
||||
我们在测试某个方法的时候可能会遇到方法内部调用了网络通信能力,网络请求成功,可能刷新 UI 或者给出一些成功的提示;网络失败或者网络不可用则给出一些失败的提示。所以需要对网络通信去看进行模拟。
|
||||
@@ -901,8 +843,6 @@ iOS 中很多网络都是基于 NSURL 系统下的类实现的。所以我们可
|
||||
|
||||
> Stub your network requests easily! Test your apps with fake network data and custom response time, response code and headers!
|
||||
|
||||
|
||||
|
||||
几个主要类及其功能:`HTTPStubsProtocol` 拦截网络请求;`HTTPStubs` 单例管理 `HTTPStubsDescriptor` 实例对象;`HTTPStubsResponse` 伪造 HTTP 请求。
|
||||
|
||||
`HTTPStubsProtocol` 继承自 `NSURLProtocol`,可以在 HTTP 请求发送之前对 request 进行过滤处理
|
||||
@@ -947,7 +887,7 @@ describe(@"routerTests", ^{
|
||||
pagePath = [routerManager filePathOfUrl:@"http://***/resource1"];
|
||||
});
|
||||
[[expectFutureValue(pagePath) shouldEventuallyBeforeTimingOutAfter(5)] beNonNil];
|
||||
|
||||
|
||||
__block NSString *rescPath = nil;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
rescPath = [routerManager filePathOfUrl:@"http://***/resource1"];
|
||||
@@ -978,8 +918,6 @@ SPEC_END
|
||||
|
||||
😂 插一嘴,我贴的代码已经好几次可以看到不同的测试框架组合了,所以不是说选了框架 A 就完事,根据场景选择最优解。
|
||||
|
||||
|
||||
|
||||
## 六、UI 测试
|
||||
|
||||
上面文章大篇幅的讲了单元测试相关的话题,单元测试十分适合代码质量、逻辑、网络等内容的测试,但是针对最终产物 App 来说单元测试就不太适合了,如果测试 UI 界面的正确性、功能是否正确显然就不太适合了。Apple 在 Xcode 7 开始推出的 `UI Testing` 就是苹果自己的 UI 测试框架。
|
||||
@@ -1005,7 +943,7 @@ Accessibility 通过对 UI 元素进行分类和标记。分类成类似按钮
|
||||
- `XCUIApplication launch` 来启动测试。`XCUIApplication` 是 UIApplication 在测试进程中的代理,用来和 App 进行一些交互。
|
||||
|
||||
- 使用 `staticTexts`来获取当前屏幕上的静态文本(UILabel)元素的代理。等价于 `[app descendantsMatchingType:XCUIElementTypeStaticText]`。XCUIElementTypeStaticText 参数是枚举类型。
|
||||
|
||||
|
||||
```objective-c
|
||||
typedef NS_ENUM(NSUInteger, XCUIElementType) {
|
||||
XCUIElementTypeAny = 0,
|
||||
@@ -1095,32 +1033,25 @@ Accessibility 通过对 UI 元素进行分类和标记。分类成类似按钮
|
||||
```
|
||||
|
||||
- 通过 `XCUIApplication` 实例化对象调用 `descendantsMatchingType:` 方法得到的是 `XCUIElementQuery` 类型。比如 `@property (readonly, copy*) XCUIElementQuery *staticTexts;`
|
||||
|
||||
|
||||
```objective-c
|
||||
/*! Returns a query for all descendants of the element matching the specified type. */
|
||||
- (XCUIElementQuery *)descendantsMatchingType:(XCUIElementType)type;
|
||||
```
|
||||
|
||||
- `descendantsMatchingType` 返回所有后代的类型匹配对象。`childrenMatchingType` 返回当前层级子元素的类型匹配对象
|
||||
|
||||
|
||||
```objective-c
|
||||
/*! Returns a query for direct children of the element matching the specified type. */
|
||||
- (XCUIElementQuery *)childrenMatchingType:(XCUIElementType)type;
|
||||
|
||||
```
|
||||
|
||||
- 拿到 `XCUIElementQuery` 后不能直接拿到 `XCUIElement`。和 `XCUIApplication` 类似,`XCUIElement` 不能直接访问 UI 元素,它是 UI 元素在测试框架中的代理。可以通过 `Accessibility` 中的 `frame`、`identifier` 来获取。
|
||||
|
||||
|
||||
|
||||
对比很多自动化测试框架都需要找出 UI 元素,也就是借助于 `Accessibility` 的 `identifier`。这里的唯一标识生成对比[为 UIAutomation 添加自动化测试标签的探索](http://yulingtianxia.com/blog/2016/03/28/Add-UITest-Label-for-UIAutomation/)]
|
||||
|
||||
|
||||
|
||||
第三方 UI 自动化测试框架挺多的,可以查看下典型的 [appium](https://github.com/appium/appium)、[macaca](https://github.com/alibaba/macaca)。
|
||||
|
||||
|
||||
|
||||
## 七、 测试经验总结
|
||||
|
||||
TDD 写好测试再写业务代码,BDD 先写实现代码,再写基于行为的测试代码。另一种思路是没必要针对每个类的私有方法或者每个方法进行测试,因为等全部功能做完后针对每个类的接口测试,一般会覆盖据大多数的方法。等测试完看如果方法未被覆盖,则针对性的补充 `Unit Test`。
|
||||
@@ -1139,15 +1070,6 @@ UITesting 还是建议在核心逻辑且长时间没有改动的情况下去做
|
||||
|
||||
WWDC 这张图也很清楚,UI 其实需要的占比较小,还是要靠单测驱动。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [维基百科:测试驱动开发](https://zh.wikipedia.org/wiki/测试驱动开发)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user