关于ios:写好测试提升应用质量

8次阅读

共计 32841 个字符,预计需要花费 83 分钟才能阅读完成。

置信在国内一些中小型公司,开发者很少会去写软件测试相干的代码。当然这背地有一些起因在。本文就讲讲 iOS 开发中的软件测试相干的内容。

一、测试的重要性

测试很重要!测试很重要!测试很重要!重要的事件说三遍。

场景 1:每次咱们写完代码后都须要编译运行,以查看应用程序的体现是否合乎预期。如果改变点、代码量小,那验证成本低一些,如果不合乎预期,则阐明咱们的代码有问,人工去排查问题破费的工夫也少一些。如果改变点很多、受影响的中央较多,咱们首先要大略猜想受影响的性能,而后去定位问题、排查问题的老本就很高。

场景 2:你新接手的 SDK 某个子性能须要做一次技术重构。然而你只有在公司外部的代码托管平台上能够看到一些 Readme、接入文档、零碎设计文档、技术计划评估文档等一堆文档。可能你会看完再去入手重构。当你重构完了,找了公司某条业务线的 App 接入测试,点了几下发现产生了奔溃。???? 心想,本地测试、debug 都失常可是为什么接入后就 Crash 了。其实想想也好了解,你本地重构只是确保了你开发的那个性能运行失常,你很难确保你写的代码没有影响其余类、其余性能。如果之前的 SDK 针对每个类都有单元测试代码,那你在新性能开发结束后残缺跑一次单元测试代码就好了,保障每个 Unit Test 都通过、分支覆盖率达到约定的线,那么基本上是没问题的。

场景 3:在版本迭代的时候,打算性能 A,从开发、联调、测试、上线共 2 周工夫。老司机做事很自信,这么简略的 UI、动画、交互,代码风骚,参考服务端的「畛域驱动」在该 feature 开发阶段落地试验了下。联调、本地测试都通过了,还剩 3 天工夫,本认为测试 1 天,bug fix 一天,最初一天提交审核。代码跟你开了个玩笑,测试完 n 个 bug(大大超出预期)。为了不影响 App 的公布上架,不得不熬夜修 bug。将所有的测试都通过测试工程师去解决,这个阶段实践上品质应该很稳固,不然该阶段发现代码异样、技术设计有破绽就来不及了,你须要协调各个团队的资源(可能接口要改变、产品侧要改变),这个阶段造成改变的老本十分大。

置信大多数开发者都遇到过上述场景的问题。其实上述这几个问题都有解,那就是“软件测试”。

二、软件测试

1. 分类

软件测试就是在规定的条件下对应用程序进行操作,以发现程序谬误,掂量软件品质,并对其是否能满足设计要求进行评估的过程。

正当应用软件测试技术,就能够躲避掉第一局部的 3 个场景下的问题。

软件测试强调开发、测试同步进行,甚至是测试后行,从需要评审阶段就先思考好软件测试计划,随后才进行技术计划评审、开发编码、单元测试、集成测试、零碎测试、回归测试、验收测试等。

软件测试从测试范畴分为:单元测试、集成测试、零碎测试、回归测试、验收测试(有些公司谈判到“冒烟测试“,这个词的准确定义不晓得,然而学软件测试课的时候依照范畴就只有上述几个分类)。工程师本人负责的是单元测试。测试工程师、QA 负责的是集成测试、零碎测试。

单元测试(Unit Testing):又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性测验的测试工作。「单元」的概念会比拟形象,它不仅仅是咱们所编写的某个办法、函数,也可能是某个类、对象等。

软件测试从开发模式分为:面向测试驱动开发 TDD(Test-driven development)、面向行为驱动开发 BDD(Behavior-driven development)。

2. TDD

TDD 的思维是:先编写测试用例,再疾速开发代码,而后在测试用例的保障下,能够不便平安地进行代码重构,晋升应用程序的品质。一言以蔽之就是通过测试来推动开发的进行。正是因为这个特点,TDD 被宽泛应用于麻利开发。

也就是说 TDD 模式下,首先思考如何针对性能进行测试,而后去编写代码实现,再一直迭代,在测试用例的保障下,一直进行代码优化。

长处:指标明确、架构分层清晰。可保障开发代码不会偏离需要。每个阶段继续测试

毛病:技术计划须要先评审完结、架构须要提前搭建好。如果需要变动,则后面步骤须要从新执行,灵活性较差。

3. BDD

BDD 即行为驱动开发,是麻利开发 技术 之一,通过自然语言定义零碎行为,以性能使用者的角度,编写需要场景,且这些行为形容能够间接造成需要文档,同时也是测试规范。

BDD 的思维是跳出繁多的函数,针对的是行为而开展的测试。BDD 关怀的是业务畛域、行为形式,而不是具体的函数、办法,通过对行为的形容来验证性能的可用性。BDD 应用 DSL(Domin Specific Language)畛域特定语言来形容测试用例,这样编写的测试用例十分易读,看起来跟文档一样易读,BDD 的代码构造是 Given->When->Then

长处:各团队的成员能够集中在一起,设计基于行为的计测试用例。

4. 比照

依据特点也就是找到了各自的应用场景,TDD 次要针对开发中的最小单元进行测试,适宜单元测试。而 BDD 针对的是行为,所以测试范畴能够再大一些,在集成测试、零碎测试中都能够应用

TDD 编写的测试用例个别针对的是开发中的最小单元(比方某个类、函数、办法)而开展,适宜单元测试。

BDD 编写的测试用例针对的是行为,测试范畴更大一些,适宜集成测试、零碎测试阶段。

三、单元测试编码标准

本文的次要重点是针对日常开发阶段工程师能够做的事件,也就是单元测试而开展。

编写性能、业务代码的时候个别会遵循 kiss 准则,所以类、办法、函数往往不会太大,分层设计越好、职责越繁多、耦合度越低的代码越适宜做单元测试,单元测试也倒逼开发过程中代码分层、解耦。

可能某个性能的实现代码有 30 行,测试代码有 50 行。单元测试的代码如何编写才更正当、整洁、标准呢?

1. 编码分模块开展

先贴一段代码。

-  (void)testInsertDataInOneSpecifiedTable
{XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库插入性能"];
    // given
    [dbInstance removeAllLogsInTableType:PCTLogTableTypeMeta];
    NSMutableArray *insertModels = [NSMutableArray array];
    for (NSInteger index = 1; index <= 10000; index++) {PCTLogMetaModel *model = [[PCTLogMetaModel alloc] init];
        model.log_id = index;
                // ...
        [insertModels addObject:model];
    }
    // when
    [dbInstance add:insertModels inTableType:PCTLogTableTypeMeta];
       // then 
      [dbInstance recordsCountInTableType:PCTLogTableTypeMeta completion:^(NSInteger count) {XCTAssert(count == insertModels.count, @"「数据减少」性能:异样");
        [exception fulfill];
    }];
    [self waitForExpectationsWithCommonTimeout];
}

能够看到这个办法的名称为 testInsertDataInOneSpecifiedTable,这段代码做的事件通过函数名能够看进去:测试插入数据到某个特定的表。这个测试用例分为 3 局部:测试环境所需的先决条件筹备;调用所要测试的某个办法、函数;验证输入和行为是否合乎预期。

其实,每个测试用例的编写也要依照该种形式去组织代码。步骤分为 3 个阶段:Given->When->Then。

所以单元测试的代码标准也就进去了。此外单元测试代码标准对立后,每个人的测试代码都依照这个规范开展,那其他人的浏览起来就更加容易、不便。依照这 3 个步骤去浏览、了解测试代码,就能够清晰明了的晓得在做什么。

2. 一个测试用例只测试一个分支

咱们写的代码有很多语句组成,有各种逻辑判断、分支 (if…else、swicth) 等等,因而一个程序从一个繁多入口进去,过程可能产生 n 个不同的分支,然而程序的进口总是一个。所以因为这样的个性,咱们的测试也须要针对这样的现状走完尽可能多的分支。相应的指标叫做「分支覆盖率」。

如果某个办法外部有 if…else…,咱们在测试的时候尽量将每种状况写成一个独自的测试用例,独自的输出、输入,判断是否合乎预期。这样每个 case 都繁多的测试某个分支,可读性也很高。

比方对上面的函数做单元测试,测试用例设计如下

- (void)shouldIEatSomething
{BOOL shouldEat = [self getAteWeight] < self.dailyFoodSupport;
   if (shouldEat) {[self eatSomemuchFood];
   } else {[self doSomeExercise];
   }
}
- (void)testShouldIEatSomethingWhenHungry
{// ....}

- (void)testShouldIEatSomethingWhenFull
{// ...}

3. 明确标识被测试类

这条次要站在团队单干和代码可读性角度登程来阐明。写过单元测试的人都晓得,可能某个函数原本就 10 行代码,可是为了测试它,测试代码写了 30 行。一个办法这样写问题不大,多看看就看明确是在测试哪个类的哪个办法。可是当这个类自身就很大,测试代码很大的状况下,不论是作者本身还是多年后负责保护的其余共事,看这个代码浏览老本会很大,须要先看测试文件名 代码类名 + Test 才晓得是测试的是哪个类,看测试方法名 test + 办法名 才晓得是测试的是哪个办法。

这样的代码可读性很差,所以应该为以后的测试对象非凡标记,这样测试代码可读性越强、浏览老本越低。比方定义局部变量 _sut 用来标记以后被测试类 (sut,System under Test,软件测试畛域有个词叫做 被测系统,用来示意正在被测试的零碎)。

#import <XCTest/XCTest.h>
#import "PCTLogPayloadModel.h"

@interface PCTLogPayloadModelTest : PCTTestCase
{PCTLogPayloadModel *_sut;}

@end

@implementation PCTLogPayloadModelTest

- (void)setUp
{[super setUp];
    PCTLogPayloadModel *model = [[PCTLogPayloadModel alloc] init];
    model.log_id = 1;
    // ...
    _sut = model;
}

- (void)tearDown
{
    _sut = nil;
    [super tearDown];
}

- (void)testGetDictionary
{NSDictionary *payloadDictionary = [_sut getDictionary];
    XCTAssert([(NSString *)payloadDictionary[@"report_id"] isEqualToString:@"001"] &&
              [payloadDictionary[@"size"] integerValue] == 102 &&
              [(NSString *)payloadDictionary[@"meta"] containsString:@"meiying"],
              @"PCTLogPayloadModel 的「getDictionary」性能异样");
}

@end

4. 应用分类来裸露公有办法、公有变量

某些场景下写的测试方法外部可能须要调用被测对象的公有办法,也可能须要拜访被测对象的某个公有属性。然而测试类外面是拜访不到被测类的公有属性和公有办法的,借助于 Category 能够实现这样的需要。

为测试类增加一个分类,后缀名为 UnitTest。如下所示

HermesClient 类有公有属性 @property (nonatomic, strong) NSString *name;,公有办法 - (void)hello。为了在测试用例中拜访公有属性和公有办法,写了如下分类

// HermesClientTest.m

@interface HermesClient (UnitTest)

- (NSString *)name;

- (void)hello;

@end
  
@implementation HermesClientTest

- (void)testPrivatePropertyAndMethod
{NSLog(@"%@",[HermesClient sharedInstance].name);
    [[HermesClient sharedInstance] hello];
}
@end

四、单元测试下开发模式、技术框架抉择

单元测试是依照测试范畴来划分的。TDD、BDD 是依照开发模式来划分的。因而就有各种排列组合,这里咱们只关怀单元测试下的 TDD、BDD 计划。

在单元测试阶段,TDD 和 BDD 都能够实用。

1. TDD

TDD 强调一直的测试推动代码的开发,这样 简化了 代码,保障了代码品质。

思维是在拿到一个新的性能时,首先思考该性能如何测试,各种测试用例、各种边界 case;而后实现测试代码的开发;最初编写相应的代码以满足、通过这些测试用例。

TDD 开发过程相似下图:

  • 先编写该性能的测试用例,实现测试代码。这时候去跑测试,是不通过的,也就是到了红色的状态
  • 而后编写真正的性能实现代码。这时候去跑测试,测试通过,也就是到了绿色的状态
  • 在测试用例的保障下,能够重构、优化代码

抛出一个问题:TDD 看上去很好,应该用它吗?

这个问题不必焦急答复,答复了也不会有对错之分。开发中常常是这样一个流程,新的需要进去后,先通过技术评审会议,确定宏观层面的技术计划、确定各个端的技术实现、应用的技术等,整顿出开发文档、会议文档。工期评估后开始编码。事件这么简略吗?后期即便想的再充沛、再粗疏,可能还是存在非凡 case 漏掉的状况,导致技术计划或者是技术实现的扭转。如果采纳 TDD,那么之前新性能给到后,就要思考测试用例的设计、编写了测试代码,在测试用例的保障下再去实现性能。如果遇到了技术计划的变更,之前的测试用例要扭转、测试代码实现要扭转。可能新增的某个 case 导致大部分的测试代码和实现代码都要扭转。

如何发展 TDD**

  1. 新建一个工程,确保“Include Unit Tests”选项是选中的状态

  2. 创立后的工程目录如下

  3. 删除 Xcode 创立的测试模版文件 TDDDemoTests.m
  4. 如果咱们须要设计一个人类,它具备吃饭的性能,且当他吃完后会说一句“好饱啊”。
  5. 那么依照 TDD 咱们先设计测试用例。假如有个 Person 类,有个对象办法叫做吃饭,吃完饭后会返回一个“好饱啊”的字符串。那测试用例就是

    步骤 冀望 后果
    实例化 Person 对象,调用对象的 eat 办法 调用后返回“好饱啊”
  6. 实现测试用例代码。创立继承自 Unit Test Case class 的测试类,命名为 工程前缀 + 测试类名 +Test,也就是 TDDPersonTest.m

  7. 因为要测试 Person 类,所以在主工程中创立 Person 类
  8. 因为要测试人类在吃饭后说一句“好饱啊”。所以构想那个类目前只有一个吃饭的办法。于是在 TDDPersonTest.m 中创立一个测试函数 -(void)testReturnStatusStringWhenPersonAte;函数内容如下

    - (void)testReturnStatusStringWhenPersonAte
    {
        // Given
        Person *somebody = [[Person alloc] init];
        
        // When
        NSString *statusMessage = [somebody performSelector:@selector(eat)];
        
        // Then
        XCTAssert([statusMessage isEqualToString:@"好饱啊"], @"Person「吃饭后返回“好饱啊”」性能异样");
    }
  9. Xcode 下按快捷键 Command + U, 跑测试代码发现是失败的。因为咱们的 Person 类基本没实现相应的办法
  10. 从 TDD 开发过程能够看到,咱们当初是红色的“Fail”状态。所以须要去 Person 类中实现性能代码。Person 类如下

    #import "Person.h"
    
    @implementation Person
    
    - (NSString *)eat
    {[NSThread sleepForTimeInterval:1];
        return @"好饱啊";;
    }
    
    @end
  11. 再次运行,跑一下测试用例(Command + U 快捷键)。发现测试通过,也就是 TDD 开发过程中的绿色“Success”状态。
  12. 例子比较简单,如果状况须要,能够在 -(void)setUp 办法外面做一些测试的前置筹备工作,在 -(void)tearDown 办法里做资源开释的操作
  13. 如果 eat 办法实现的不够丑陋。当初在测试用例的保障下,大胆重构,最初确保所有的 Unit Test case 通过即可。

2. BDD

相比 TDD,BDD 关注的是行为形式的设计,拿上述“人吃饭”举例说明。

和 TDD 相比第 1~4 步骤雷同。

  1. BDD 则须要先实现性能代码。创立 Person 类,实现 -(void)eat;办法。代码和下面的雷同
  2. BDD 须要引入好用的框架 Kiwi,应用 Pod 的形式引入
  3. 因为要测试人类在吃饭后说一句“好饱啊”。所以构想那个类目前只有一个吃饭的办法。于是在 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 的程序开展。测试环境所需的先决条件筹备;调用所要测试的某个办法、函数;应用断言验证输入和行为是否合乎预期。
  • - (void)tearDown 办法外面写一些开释掉资源或者敞开的代码。比方测试数据库性能的时候,写一些数据库连接池敞开的代码

断言相干宏

/*!
 * @function 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__)

教训小结

  1. XCTestCase 类和其余类一样,你能够定义基类,这外面封装一些罕用的办法。

    // PCTTestCase.h
    #import <XCTest/XCTest.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface PCTTestCase : XCTestCase
    
    @property (nonatomic, assign) NSTimeInterval networkTimeout;
    
    
    /**
     用一个默认工夫设置异步测试 XCTestExpectation 的超时解决
     */
    - (void)waitForExpectationsWithCommonTimeout;
    
    /**
     用一个默认工夫设置异步测试的
    
     @param handler 超时的解决逻辑
     */
    - (void)waitForExpectationsWithCommonTimeoutUsingHandler:(XCWaitCompletionHandler __nullable)handler;
    
    
    /**
     生成 Crash 类型的 meta 数据
    
     @return meta 类型的字典
     */
    - (NSDictionary *)generateCrashMetaDataFromReport;
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    // PCTTestCase.m
    #import "PCTTestCase.h"
    #import ...
    
    @implementation PCTTestCase
    
    #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,这些是测试中十分常见且重要的性能

例子

这里举个例子,是测试一个数据库操作类 PCTDatabase,代码只放某个办法的测试代码。

- (void)testRemoveLatestRecordsByCount
{XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库删除最新数据性能"];
    // 1. 先清空数据表
    [dbInstance removeAllLogsInTableType:PCTLogTableTypeMeta];
    // 2. 再插入一批数据
    NSMutableArray *insertModels = [NSMutableArray array];
    NSMutableArray *reportIDS = [NSMutableArray array];
    
    for (NSInteger index = 1; index <= 100; index++) {PCTLogMetaModel *model = [[PCTLogMetaModel alloc] init];
        model.log_id = index;
        // ...
        if (index > 90 && index <= 100) {[reportIDS addObject:model.report_id];
        }
        [insertModels addObject:model];
    }
    [dbInstance add:insertModels inTableType:PCTLogTableTypeMeta];
    
    // 3. 将晚期的数据删除掉(id > 90 && id <= 100)[dbInstance removeLatestRecordsByCount:10 inTableType:PCTLogTableTypeMeta];
    
    // 4. 拿到以后的前 10 条数据和之前存起来的前 10 条 id 做比拟。再判断以后表中的总记录条数是否等于 90
    [dbInstance getLatestRecoreds:10 inTableType:PCTLogTableTypeMeta completion:^(NSArray<PCTLogModel *> * _Nonnull records) {
        NSArray<PCTLogModel *> *latestRTentRecords = records;
        
        [dbInstance getOldestRecoreds:100 inTableType:PCTLogTableTypeMeta completion:^(NSArray<PCTLogModel *> * _Nonnull records) {
            NSArray<PCTLogModel *> *currentRecords = records;
            
            __block BOOL isEarlyData = NO;
            [latestRTentRecords enumerateObjectsUsingBlock:^(PCTLogModel * _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_BEGINSPEC_END 示意测试类;describe 形容须要被测试的类;context 示意一个测试场景,也就是 Given->When->Then 里的 Givenit 示意要测试的内容,也就是也就是 Given->When->Then 里的 WhenThen。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)waitForExpectationsWithTimeout:(NSTimeInterval)timeout handler:(nullable XCWaitCompletionHandler)handler; 如下例子

XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库插入性能"];
    [dbInstance removeAllLogsInTableType:PCTLogTableTypeMeta];
    NSMutableArray *insertModels = [NSMutableArray array];
    for (NSInteger index = 1; index <= 10000; index++) {PCTLogMetaModel *model = [[PCTLogMetaModel alloc] init];
        model.log_id = index;
          //。。。[insertModels addObject:model];
    }
    [dbInstance add:insertModels inTableType:PCTLogTableTypeMeta];
    [dbInstance recordsCountInTableType:PCTLogTableTypeMeta 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 的断言则更加全面一些。

没方法说哪个最好、最正当,依据我的项目需要抉择适合的组合。

五、网络测试

咱们在测试某个办法的时候可能会遇到办法外部调用了网络通信能力,网络申请胜利,可能刷新 UI 或者给出一些胜利的提醒;网络失败或者网络不可用则给出一些失败的提醒。所以须要对网络通信去看进行模仿。

iOS 中很多网络都是基于 NSURL 零碎下的类实现的。所以咱们能够利用 NSURLProtocol 的能力来监控网络并 mock 网络数据。如果感兴趣能够查看这篇文章。

开源我的项目 OHHTTPStubs 就是一个对网络模仿的库。它能够拦挡 HTTP 申请,返回 json 数据,定制各种头信息。

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 进行过滤解决

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{BOOL found = ([HTTPStubs.sharedInstance firstStubPassingTestForRequest:request] != nil);
   if (!found && HTTPStubs.sharedInstance.onStubMissingBlock) {HTTPStubs.sharedInstance.onStubMissingBlock(request);
   }
   return found;
}

firstStubPassingTestForRequest 办法外部会判断申请是否须要被以后对象解决

紧接着开始发送网络申请。实际上在 - (void)startLoading 办法中能够用任何网络能力去实现申请,比方 NSURLSession、NSURLConnection、AFNetworking 或其余网络框架。OHHTTPStubs 的做法是获取 request、client 对象。如果 HTTPStubs 单例中蕴含 onStubActivationBlock 对象,则执行该 block,而后利用 responseBlock 对象返回一个 HTTPStubsResponse 响应对象。

OHHTTPStubs 的具体 API 能够查看文档。

举个例子,利用 Kiwi、OHHTTPStubs 测试离线包性能。代码如下

@interface HORouterManager (Unittest)

- (void)fetchOfflineInfoIfNeeded;

@end

SPEC_BEGIN(HORouterTests)

describe(@"routerTests", ^{
    context(@"criticalPath", ^{
        __block HORouterManager *routerManager = nil;
        beforeAll(^{routerManager = [[HORouterManager alloc] init];
        });
        it(@"getLocalPath", ^{
            __block NSString *pagePath = nil;
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{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"];
            });
            [[expectFutureValue(rescPath) shouldEventuallyBeforeTimingOutAfter(5)] beNonNil];
        });
        it(@"fetchOffline", ^{[HOOfflineManager sharedInstance].offlineInfoInterval = 0;
            [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {return [request.URL.absoluteString containsString:@"h5-offline-pkg"];
            } withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {NSMutableDictionary *dict = [NSMutableDictionary dictionary];
                dict[@"code"] = @(0);
                dict[@"data"] = @"f722fc3efce547897819e9449d2ac562cee9075adda79ed74829f9d948e2f6d542a92e969e39dfbbd70aa2a7240d6fa3e51156c067e8685402727b6c13328092ecc0cbc773d95f9e0603b551e9447211b0e3e72648603e3d18e529b128470fa86aeb45d16af967d1a21b3e04361cfc767b7811aec6f19c274d388ddae4c8c68e857c14122a44c92a455051ae001fa7f2b177704bdebf8a2e3277faf0053460e0ecf178549e034a086470fa3bf287abbdd0f79867741293860b8a29590d2c2bb72b749402fb53dfcac95a7744ad21fe7b9e188881d1c24047d58c9fa46b3ebf4bc42a1defc50748758b5624c6c439c182fe21d4190920197628210160cf279187444bd1cb8707362cc4c3ab7486051af088d7851846bea21b64d4a5c73bd69aafc4bb34eb0862d1525c4f9a62ce64308289e2ecbc19ea105aa2bf99af6dd5a3ff653bbe7893adbec37b44a088b0b74b80532c720c79b7bb59fda3daf85b34ef35";
                NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil];
                return [OHHTTPStubsResponse responseWithData:data
                                                  statusCode:200
                                                     headers:@{@"Content-Type":@"application/json"}];
            }];
            [routerManager fetchOfflineInfoIfNeeded];
            [[HOOfflineInfo shouldEventually] receive:@selector(saveToLocal:)];
        });
    });
});

SPEC_END

???? 插一嘴,我贴的代码曾经好几次能够看到不同的测试框架组合了,所以不是说选了框架 A 就完事,依据场景抉择最优解。

六、UI 测试

下面文章大篇幅的讲了单元测试相干的话题,单元测试非常适宜代码品质、逻辑、网络等内容的测试,然而针对最终产物 App 来说单元测试就不太适宜了,如果测试 UI 界面的正确性、性能是否正确显然就不太适宜了。Apple 在 Xcode 7 开始推出的 UI Testing 就是苹果本人的 UI 测试框架。

很多 UI 自动化测试框架的底层实现都依赖于 Accessibility,也就是 App 可用性。UI Accessibility 是 iOS 3.0 引入的一个人性化性能,帮忙身材不便的人士方便使用 App。

Accessibility 通过对 UI 元素进行分类和标记。分类成相似按钮、文本框、文本等类型,应用 identifier 来辨别不同 UI 元素。无痕埋点的设计与实现外面也应用 accessibilityIdentifier 来绑定业务数据。

  1. 应用 Xcode 自带的 UI 测试则在创立工程的时候须要勾选“Include UI Tests”。
  2. 像单元测试意义,UI 测试方法命名以 test 结尾。将鼠标光标移到办法内,点击 Xcode 左下方的红色按钮,开始录制 UI 脚本。

解释阐明:

/*! Proxy for an application that may or may not be running. */
@interface XCUIApplication : XCUIElement
// ...
@end
  • XCUIApplication launch 来启动测试。XCUIApplication 是 UIApplication 在测试过程中的代理,用来和 App 进行一些交互。
  • 应用 staticTexts来获取以后屏幕上的动态文本(UILabel)元素的代理。等价于 [app descendantsMatchingType:XCUIElementTypeStaticText]。XCUIElementTypeStaticText 参数是枚举类型。

    typedef NS_ENUM(NSUInteger, XCUIElementType) {
        XCUIElementTypeAny = 0,
        XCUIElementTypeOther = 1,
        XCUIElementTypeApplication = 2,
        XCUIElementTypeGroup = 3,
        XCUIElementTypeWindow = 4,
        XCUIElementTypeSheet = 5,
        XCUIElementTypeDrawer = 6,
        XCUIElementTypeAlert = 7,
        XCUIElementTypeDialog = 8,
        XCUIElementTypeButton = 9,
        XCUIElementTypeRadioButton = 10,
        XCUIElementTypeRadioGroup = 11,
        XCUIElementTypeCheckBox = 12,
        XCUIElementTypeDisclosureTriangle = 13,
        XCUIElementTypePopUpButton = 14,
        XCUIElementTypeComboBox = 15,
        XCUIElementTypeMenuButton = 16,
        XCUIElementTypeToolbarButton = 17,
        XCUIElementTypePopover = 18,
        XCUIElementTypeKeyboard = 19,
        XCUIElementTypeKey = 20,
        XCUIElementTypeNavigationBar = 21,
        XCUIElementTypeTabBar = 22,
        XCUIElementTypeTabGroup = 23,
        XCUIElementTypeToolbar = 24,
        XCUIElementTypeStatusBar = 25,
        XCUIElementTypeTable = 26,
        XCUIElementTypeTableRow = 27,
        XCUIElementTypeTableColumn = 28,
        XCUIElementTypeOutline = 29,
        XCUIElementTypeOutlineRow = 30,
        XCUIElementTypeBrowser = 31,
        XCUIElementTypeCollectionView = 32,
        XCUIElementTypeSlider = 33,
        XCUIElementTypePageIndicator = 34,
        XCUIElementTypeProgressIndicator = 35,
        XCUIElementTypeActivityIndicator = 36,
        XCUIElementTypeSegmentedControl = 37,
        XCUIElementTypePicker = 38,
        XCUIElementTypePickerWheel = 39,
        XCUIElementTypeSwitch = 40,
        XCUIElementTypeToggle = 41,
        XCUIElementTypeLink = 42,
        XCUIElementTypeImage = 43,
        XCUIElementTypeIcon = 44,
        XCUIElementTypeSearchField = 45,
        XCUIElementTypeScrollView = 46,
        XCUIElementTypeScrollBar = 47,
        XCUIElementTypeStaticText = 48,
        XCUIElementTypeTextField = 49,
        XCUIElementTypeSecureTextField = 50,
        XCUIElementTypeDatePicker = 51,
        XCUIElementTypeTextView = 52,
        XCUIElementTypeMenu = 53,
        XCUIElementTypeMenuItem = 54,
        XCUIElementTypeMenuBar = 55,
        XCUIElementTypeMenuBarItem = 56,
        XCUIElementTypeMap = 57,
        XCUIElementTypeWebView = 58,
        XCUIElementTypeIncrementArrow = 59,
        XCUIElementTypeDecrementArrow = 60,
        XCUIElementTypeTimeline = 61,
        XCUIElementTypeRatingIndicator = 62,
        XCUIElementTypeValueIndicator = 63,
        XCUIElementTypeSplitGroup = 64,
        XCUIElementTypeSplitter = 65,
        XCUIElementTypeRelevanceIndicator = 66,
        XCUIElementTypeColorWell = 67,
        XCUIElementTypeHelpTag = 68,
        XCUIElementTypeMatte = 69,
        XCUIElementTypeDockItem = 70,
        XCUIElementTypeRuler = 71,
        XCUIElementTypeRulerMarker = 72,
        XCUIElementTypeGrid = 73,
        XCUIElementTypeLevelIndicator = 74,
        XCUIElementTypeCell = 75,
        XCUIElementTypeLayoutArea = 76,
        XCUIElementTypeLayoutItem = 77,
        XCUIElementTypeHandle = 78,
        XCUIElementTypeStepper = 79,
        XCUIElementTypeTab = 80,
        XCUIElementTypeTouchBar = 81,
        XCUIElementTypeStatusItem = 82,
    };
  • 通过 XCUIApplication 实例化对象调用 descendantsMatchingType: 办法失去的是 XCUIElementQuery 类型。比方 @property (readonly, copy*) XCUIElementQuery *staticTexts;

    /*! Returns a query for all descendants of the element matching the specified type. */
    - (XCUIElementQuery *)descendantsMatchingType:(XCUIElementType)type;
  • descendantsMatchingType 返回所有后辈的类型匹配对象。childrenMatchingType 返回以后层级子元素的类型匹配对象

    /*! Returns a query for direct children of the element matching the specified type. */
    - (XCUIElementQuery *)childrenMatchingType:(XCUIElementType)type;
    
  • 拿到 XCUIElementQuery 后不能间接拿到 XCUIElement。和 XCUIApplication 相似,XCUIElement 不能间接拜访 UI 元素,它是 UI 元素在测试框架中的代理。能够通过 Accessibility 中的 frameidentifier 来获取。

比照很多自动化测试框架都须要找出 UI 元素,也就是借助于 Accessibilityidentifier。这里的惟一标识生成比照为 UIAutomation 增加自动化测试标签的摸索]

第三方 UI 自动化测试框架挺多的,能够查看下典型的 appium、macaca。

七、测试经验总结

TDD 写好测试再写业务代码,BDD 先写实现代码,再写基于行为的测试代码。另一种思路是没必要针对每个类的公有办法或者每个办法进行测试,因为等全副性能做完后针对每个类的接口测试,个别会笼罩据大多数的办法。等测试完看如果办法未被笼罩,则针对性的补充 Unit Test

目前,UI 测试(appium)还是倡议在外围逻辑且长时间没有改变的状况上来做,这样子每次发版本的时候能够当作外围逻辑回归了,目前来看价值是不便后续的迭代和保护上有一些便利性。其余的功能性测试还是走 BDD。

对于类、函数、办法的走 TDD,老老实实写 UT、走 UT 覆盖率的把控。

UITesting 还是倡议在外围逻辑且长时间没有改变的状况上来做,这样子每次发版本的时候能够当作外围逻辑回归,目前来看价值是不便后续的迭代和保护上有一些便利性。例如用户核心 SDK 降级后,过后有了 UITesing,基本上免去了测试人员染指。

如果是一些流动页和逻辑常常变动的,老老实实走测试黑盒 …

我感觉始终有个误区,就是感觉自动测试是为了品质,其实品质都是附送的,测试后行是让开发更快更爽的

WWDC 这张图也很分明,UI 其实须要的占比拟小,还是要靠单测驱动。

参考资料

  • 维基百科:测试驱动开发
正文完
 0