[S01E02] iOS单元测试

2,162 阅读3分钟

what:什么是单元测试 how: 如何进行单元测试 why: 为什么要做单元测试

一、什么是单元测试

1、单元测试

单元可以简单地理解为一个方法,那么单元测试就是针对方法的测试

2、单元测试在测试中所处的位置

一般来讲,小型测试用来保障代码质量,中、大型测试用来保障产品质量。

分类测试
小型测试这里一般值得是单元测试
中型测试逻辑层测试
大型测试UI测试/接口测试

测试金字塔 测试金字塔从下往上依赖于来越多,但是我们对整个产品质量的信心是越来越强的。我们需要为单元测试提供mock环境。 一般的,单元测试不依赖任何外部环境、网络环境、数据库环境。 单元测试是整个测试金字塔的基石。在整个测试中占据非常重要的位置。

参考:《Google软件测试之道》

二、如何进行单元测试

2.1 如何给项目新增单元测试Scheme

如果我们在创建项目的时候没有勾选include Tests,那我们就需要给项目新增测试的Scheme 新增单元测试Target

2.2 新增一个测试用例

在新建的DemoTests下面的DemoTests.m中新增一条测试用例,测试用例必须是实例方法,返回值为void并且以test开头

- (void)testExample {
    // This is an example of a functional test case.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
}

2.3 常用断言

    XCTFail(@"Fail");
    XCTAssertNil(@"not nil string", @"string must be nil");
    XCTAssertNotNil(@"not nil string", @"string can not be nil");
    XCTAssert((2 > 2), @"expression must be true");
    XCTAssertTrue(1, @"Can not be zero");
    XCTAssertFalse((2 < 2), @"expression must be false");
    XCTAssertEqualObjects(@"1", @"1", @"[a1 isEqual:a2] should return YES");
    XCTAssertNotEqualObjects(@"1", @"2", @"[a1 isEqual:a2] should return NO");
    XCTAssertEqual(1, 2, @"a1 = a2 shoud be true"); 
    XCTAssertEqual(str1, str2, @"a1 and a2 should point to the same object"); 

2.4 测试文件中各个函数执行的顺序

控制台输出的结果如下

2021-07-08 14:29:10.782883+0800 Demo[19597:31722127] 🍺 +setUp
2021-07-08 14:29:10.784474+0800 Demo[19597:31722127] 🍺 -setUp
2021-07-08 14:29:10.784667+0800 Demo[19597:31722127] 🍺 testExample1
2021-07-08 14:29:10.784857+0800 Demo[19597:31722127] 🍺 -tearDown
2021-07-08 14:29:10.785971+0800 Demo[19597:31722127] 🍺 -setUp
2021-07-08 14:29:10.786121+0800 Demo[19597:31722127] 🍺 testExample2
2021-07-08 14:29:10.786270+0800 Demo[19597:31722127] 🍺 -tearDown
2021-07-08 14:29:10.787399+0800 Demo[19597:31722127] 🍺 -setUp
2021-07-08 14:29:10.787548+0800 Demo[19597:31722127] 🍺 testExample3
2021-07-08 14:29:10.787700+0800 Demo[19597:31722127] 🍺 -tearDown
2021-07-08 14:29:10.788271+0800 Demo[19597:31722127] 🍺 +tearDown

值得注意的是,testExample1、testExample2、testExample3的执行并没有一定的顺序。

2.2 如何在Xcode中查看单元测试覆盖率

单元测试的覆盖率问题默认是关闭的,需要在Xcode中打开 edit scheme 勾选Code Coverage 查看覆盖率

三、为什么要做单元测试

  • 检查代码逻辑
  • 验证边界条件
  • 保障重构的顺利进行

3.1 检查代码逻辑

单元测试最基本的功能就是验证我们函数的逻辑是否符合预期。

3.2 验证边界条件

点赞数显示字符串转换,例如本条视频的真实点赞数字是23848999,我们显示2384.8

/// 将点赞的数量转换成字符串
/// @param count 点赞数量
/// @discussion 数字大于等于1万时,保留一位小数点
/// @discussion 数字低于1万时,展示真实数字
- (NSString *)descForLikeCount:(NSInteger)count{
    NSString *desc = nil;
    if (count >= 10000) {
        desc = [NSString stringWithFormat:@"%.1f万", (count / 10000.f)];
    }else{
        desc = [NSString stringWithFormat:@"%ld", count];
    }
    return desc;
}

上面的函数乍一看并没有什么问题,但是当我们输入99999的时候函数的返回值却是10.0万并不符合我们的预期。类似这样的错误甚至在代码评审(Code Review)中也很难被发现。

3.3 保障重构的顺利进行

定义方向枚举

typedef enum{

    DIREDRTION_UNKNOW = -1, // 未知方向
    DIREDRTION_N = 0, // 北
    DIREDRTION_E = 1, // 东
    DIREDRTION_S = 2, // 南
    DIREDRTION_W = 3 // 西

}DIREDRTION;

重构前的函数:

- (DIREDRTION)turn:(NSString *)cmd curDirection:(DIREDRTION)curDirection{
    if ([cmd isEqualToString:@"L"]){
        if (curDirection == DIREDRTION_N) {
            return DIREDRTION_W;
        }
        if (curDirection == DIREDRTION_W) {
            return DIREDRTION_S;
        }
        if (curDirection == DIREDRTION_S) {
            return DIREDRTION_E;
        }
        if (curDirection == DIREDRTION_E) {
            return DIREDRTION_N;
        }
    }else if ([cmd isEqualToString:@"R"]){
        if (curDirection == DIREDRTION_N) {
            return DIREDRTION_E;
        }
        if (curDirection == DIREDRTION_W) {
            return DIREDRTION_N;
        }
        if (curDirection == DIREDRTION_S) {
            return DIREDRTION_W;
        }
        if (curDirection == DIREDRTION_E) {
            return DIREDRTION_S;
        }
    }
    return  DIREDRTION_UNKNOW;
}

这时候我们需要一些单元测试作为保障

随着工作的深入,我们发现了一个规律

左转:最终方向 = (当前方向 + 3) % 4 右转:最终方向 = (当前方向 + 5) % 4

重构后的函数:

- (DIREDRTION)turn:(NSString *)cmd curDirection:(DIREDRTION)curDirection{
    if ([cmd isEqualToString:@"L"]) {
        DIREDRTION direction = (curDirection + 3) % 4;
        return direction;
    }
    if ([cmd isEqualToString:@"R"]) {
        DIREDRTION direction = (curDirection + 5) % 4;
        return direction;
    }
    return  DIREDRTION_UNKNOW;
}

重构完成之后,运行单元测试。依旧全部通过。证明此次重构并没有引入新的问题。

到目前为止,我想你已经单元测试坚定的拥护者了。但是我们之前说过,单元测试并不依赖网络环境、系统时间、数据库,只是单纯的对函数功能的测试,要想更好的进行单元测试,你还必须掌握mock,请继续观看下一章节《OCMock的使用》。