iOS单元测试
  U5A8HtQCnwkQ 2023年12月09日 15 0

一、简介

单元测试通常由软件开发人员自己编写,他们将确认具体功能是否按照设计要求正常工作。单元测试的目标是隔离代码的每个部分,并确保每个独立的部分都能正常工作。

例如,如果你有一个计算器应用程序,你可能会为加法、减法、乘法和除法等每个功能编写单元测试,以确保当给定特定输入时,这些功能能返回正确的结果。

这种测试方法可以帮助找出代码中的错误和问题,确保在整个软件系统中的各个单位都能正常工作,从而提高了代码质量和可维护性。

在 iOS 开发中 XCTestApple 提供的官方测试框架,用于进行单元测试、性能测试以及用户界面测试。

以下是关于 XCTest 的详细介绍:

  • 主要组件XCTest 框架包括 XCTests(单元测试)、XCUItests(用户界面测试)和 XCPPerformanceTests(性能测试)。
  • 单元测试(XCTests:这是最基础的测试类型,主要用于测试应用中的个别方法或计算逻辑是否按预期工作。
  • 用户界面测试(XCUItests:这种测试模拟用户与应用程序的交互操作,例如点击按钮、滑动屏幕等。该测试可确保当用户使用您的应用时,界面和交互功能能正常运行。
  • 性能测试(XCPPerformanceTests:这种测试帮助您量化代码的性能,并在代码更改后跟踪其变化。您可以为某些任务设置基准时间,然后在优化代码后比较新的执行时间。
  • 测试断言XCTest 提供了一套断言供你验证测试结果。这些断言包括 XCTAssertTrue(), XCTAssertFalse(),XCTAssertEqual() 等。
  • 集成和运行XCTest 完全集成在 Xcode 中,且易于使用。你可以直接从 Xcode 的测试导航器运行测试,或者使用快捷键 Cmd + U 运行所有测试。
  • 测试报告Xcode 会为执行的 XCTest

总的来说,XCTest

二、如何项目中添加

Xcode 中添加一个新的 XCTest

1、创建项目是直接创建

创建项目成功过项目目录下即可看到对应的单元测试文件夹

带有 Tests 后缀的文件夹

2、已有的项目添加 XCTest

3、运行 - (void)testExample 方法一直报错 Test Failed

报错 The bundle “...Tests” couldn’t be loaded. Try reinstalling the bundle.

需要设置 team 和 项目的 team 一致来解决

4、'XCTest/XCTest.h' file not found

问题描述:fatal error: 'XCTest/XCTest.h' file not found

解决方法

在报错的 Target 中的 Building settingsFRAMEWORK_SEARCH_PATHS 添加 $(PLATFORM_DIR)/Developer/Library/Frameworks

5、运行项目报错 Library not loaded: @rpath/XCTest.framework/XCTest

三、 方法简单概述:

setUp 方法是当前测试类的初始化方法,我们可以将一些资源准备工作在这个方法中完成;
tearDown 方式在测试结束后会调用,用来进行资源的清理。
测试函数都需要以 text 开头, testExample 是默认生成的一个测试用例函数,可以编辑 testXXX方法,只要是 test 开头的方法都可以进行检查。 xCTest 框架提供了众多测试断言,可以用来测试,命中断言,则认为当前测试用例失败。
testPerformanceExample 是性能测试的一个案例,其内的 measureBlock 里的代码会被默认执行10次,最终输出每次执行的时间消耗报告。

1.测试函数的要求是:1.必须无返回值;2.实例方法要求:没有入参,没有返回值,以 test 开头

2.测试函数执行的顺序:以函数名中 test 后面的字符大小有关,比如 -(void)test001XXX 会先于 -(void)test002XXX 执行;

3.运行单元测试的快捷键:CMD + U;

4.下面一共18个断言(SDK中也是18个,其含义转自 ios UnitTest 学习笔记,真心佩服原文的博主):

`XCTFail(format…)` 生成一个失败的测试; 

`XCTAssertNil(a1, format...)` 为空判断,a1为空时通过,反之不通过; 

`XCTAssertNotNil(a1, format…)` 不为空判断,a1不为空时通过,反之不通过;

`XCTAssert(expression, format...)` 当 `expression` 求值为 `TRUE` 时通过; 

`XCTAssertTrue(expression, format...)` 当 `expression` 求值为 `TRUE` 时通过; 

`XCTAssertFalse(expression, format...)` 当 `expression` 求值为 `False` 时通过; 

`XCTAssertEqualObjects(a1, a2, format...)` 判断相等, `[a1 isEqual:a2]` 值为TRUE时通过,其中一个不为空时,不通过;

`XCTAssertNotEqualObjects(a1, a2, format...)` 判断不等, `[a1 isEqual:a2]` 值为 `False` 时通过;

`XCTAssertEqual(a1, a2, format...)` 判断相等(当a1和a2是 C语言标量、结构体或联合体时使用,实际测试发现 `NSString` 也可以); 

`XCTAssertNotEqual(a1, a2, format...)` 判断不等(当a1和a2是 C语言标量、结构体或联合体时使用);

`XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...)` 判断相等,(`double`或`float`类型)提供一个误差范围,当在误差范围(`+/-accuracy`)以内相等时通过测试; 

`XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...)` 判断不等,(`double`或`float`类型)提供一个误差范围,当在误差范围以内不等时通过测试; 

`XCTAssertThrows(expression, format...)` 异常测试,当 `expression` 发生异常时通过;反之不通过;(很变态) 

`XCTAssertThrowsSpecific(expression, specificException, format...)` 异常测试,当 `expression` 发生 `specificException` 异常时通过;反之发生其他异常或不发生异常均不通过; 

`XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...)` 异常测试,当 `expression` 发生具体异常、具体异常名称的异常时通过测试,反之不通过; 

`XCTAssertNoThrow(expression, format…)` 异常测试,当 `expression` 没有发生异常时通过测试;

`XCTAssertNoThrowSpecific(expression, specificException, format...)` 异常测试,当 `expression` 没有发生具体异常、具体异常名称的异常时通过测试,反之不通过; 

`XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...)` 异常测试,当 `expression` 没有发生具体异常、具体异常名称的异常时通过测试,反之不通过

特别注意下 XCTAssertEqualObjectsXCTAssertEqual

`XCTAssertEqualObjects(a1, a2, format...)` 的判断条件是 `[a1 isEqual:a2]` 是否返回一个 `YES`。

`XCTAssertEqual(a1, a2, format...)` 的判断条件是`a1 == a2`是否返回一个 `YES`。

对于后者,如果a1和a2都是基本数据类型变量,那么只有a1 == a2才会返回YES。例如下面代码中只有第二行可以通过测试:

// 1.比较基本数据类型变量
XCTAssertEqual(1, 2, @"a1 = a2 shoud be true"); // 无法通过测试
XCTAssertEqual(1, 1, @"a1 = a2 shoud be true"); // 通过测试

但是,如果a1和a2都是指针,那么只有a1和a2指向同一个对象才会返回YES。例如下面的代码中:

// 3.比较NSArray对象
    NSArray *array1 = @[@1];
    NSArray *array2 = @[@1];
    NSArray *array3 = array1;
    XCTAssertEqual(array1, array2, @"a1 and a2 should point to the same object"); // 无法通过测试
    XCTAssertEqual(array1, array3, @"a1 and a2 should point to the same object"); // 通过测试

array1和array2指向不同对象,无法通过测试。

这里比较奇怪的是,NSString 另当别论:

// 2.比较NSString对象
    NSString *str1 = @"1";
    NSString *str2 = @"1";
    NSString *str3 = str1;
    XCTAssertEqual(str1, str2, @"a1 and a2 should point to the same object"); // 通过测试
    XCTAssertEqual(str1, str3, @"a1 and a2 should point to the same object"); // 通过测试

尽管str1和str2指向不同的对象,但是二者的指针比较却能通过测试。 由于str1和str2指向同一常量,常量在内存的 data 段中地址是固定的,所以二者地址相同。

四、异步函数的测试

前面我们演示的测试用例所执行的逻辑都是同步的,但在实际的项目中,异步的操作很多,

- (void)requestData:(void (^)(BOOL))complete {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        if (complete) {
            complete(YES);
        }
    });
}

测试用例如下:

- (void)testAsync {
    XCTestExpectation *except = [self expectationWithDescription:@"异步请求测试用例"];
    [self.viewModel requestData:^(BOOL success) {
        XCTAssertTrue(success);
        [except fulfill];
    }];
    [self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) {
        
    }];
}

XCTestExpectation 可以理解为一个期望对象,当使用此对象调用 fulfill 方法后,表示异步逻辑完成。

五、代码覆盖率

与单元测试相关的,还有一个重要的概念:代码覆盖率。代码覆盖率是指在整个测试执行过程中,覆盖到的功能函数与所有功能函数的比例。覆盖率越高说明测试涉及的功能越全。

测试完成后,可以直接在Xcode中查看代码覆盖率,如下图所示:

单元测试保持较高的覆盖率是非常重要的,其从另一个方面也是测试质量的保障。【当然这里是一个示例,所以没有很高的覆盖率】

如果看不到代码覆盖率,点击edit scheme,看对应的tagart-->test-->code Coverage 那个对勾都勾上了没有

六、关于单元测试(逻辑代码部分)的几点建议

不涉及UI方面的自动化测试,只针对逻辑代码的单元测试,下面这些建议可供参考:

  • 在编码时,要尽量按照MVVM的模式进行开发,相比MVC模式,MVVM的逻辑代码都封装在VM里面,更利于进行脱离UI的测试。可以设想,如果将逻辑方法都写在ViewViewController中,则执行测试用例时就不得不引入很多额外的页面UI组件。
  • 编写测试用例时,有3个核心要考虑的点,即输入输出结果判定。我们通过输入来设置测试用例的初始状态,通过对输出的结果判定来决定测试用例是否通过。
  • 在开发中,编写的函数要尽量符合下面的特性:功能单一,有输入有输出。
  • 函数有输入参数,没有返回值时,需要对输入的参数进行修改,则这种场景编写测试用例时,要判断的是执行函数操作后的原始变量是否符合预期。
  • 函数没有输入参数,没有返回值时,其作用只是执行一段逻辑操作,例如存储文件,修改文件等。这时我们可以修改下功能函数,在函数内返回操作成功或失败的结果,测试用例使用此结果来作为是否通过的标准。
七、XCTest 框架中也是有 UI 测试的

例如: 创建一个 UI Test Target

自带会有这样的方法测试启动时长性能方法

生成文件示例

运行查看启动时长

启动时长日志输出

总结:默认会联系多次启动,可以查看每次的时长,方便对比结果。

遇到的 UITest 问题 testExample: Device is not configured for UI testing - use of XCUIApplication is not supported. This can happen when XCUIApplication is used in a unit test bundle instead of a UI test bundle. (NSInternalInconsistencyException)

解决方案: 1、UI test 和 逻辑 test 是不同的 target, 可能选择错了,需要创建 UI test target

更多的使用可以参考iOS单元测试的那些事儿

原博客:https://www.jianshu.com/p/c55f364b3750?v=1702025708466

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月09日 0

暂无评论

推荐阅读
  529IrGbiySY6   2023年12月23日   50   0   0 AppUIiosAppUIios
U5A8HtQCnwkQ
作者其他文章 更多

2023-12-09