ios 单元测试

一、简介

单元测试(Unit Testing) 是一种软件测试方法,主要用于确定各个独立的软件模块是否正确。在这个过程中,开发者会对函数、程序或方法进行详细的检查以确保它们能够按预期运行。

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

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

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

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

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

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

总的来说,XCTest 是一个强大的工具,能够帮助 iOS 开发者确保他们的应用在各种情况下都可以正常工作。

二、如何项目中添加 XCTest

Xcode 中添加一个新的 XCTest 单元测试模块相对简单。下面是具体的步骤:

1、创建项目是直接创建

2023-12-07-16-20-05-image.png

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

2023-12-07-16-21-54-image.png

带有 Tests 后缀的文件夹

2、已有的项目添加 XCTest

2023-12-07-16-24-23-image.png
2023-12-07-16-25-17-image.png
2023-12-07-17-09-54-image.png

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

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

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

image.png

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

image.png

三、 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 段中地址是固定的,所以二者地址相同。

四、异步函数的测试

前面我们演示的测试用例所执行的逻辑都是同步的,但在实际的项目中,异步的操作很多,XCTest框架中也提供了异步逻辑的测试方式。例如对如下业务方法进行测试:

- (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中查看代码覆盖率,如下图所示:

image.png

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

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

image.png

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

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

  • 在编码时,要尽量按照MVVM的模式进行开发,相比MVC模式,MVVM的逻辑代码都封装在VM里面,更利于进行脱离UI的测试。可以设想,如果将逻辑方法都写在ViewViewController中,则执行测试用例时就不得不引入很多额外的页面UI组件。

  • 编写测试用例时,有3个核心要考虑的点,即输入输出结果判定。我们通过输入来设置测试用例的初始状态,通过对输出的结果判定来决定测试用例是否通过。

  • 在开发中,编写的函数要尽量符合下面的特性:功能单一,有输入有输出。

  • 函数有输入参数,没有返回值时,需要对输入的参数进行修改,则这种场景编写测试用例时,要判断的是执行函数操作后的原始变量是否符合预期。

  • 函数没有输入参数,没有返回值时,其作用只是执行一段逻辑操作,例如存储文件,修改文件等。这时我们可以修改下功能函数,在函数内返回操作成功或失败的结果,测试用例使用此结果来作为是否通过的标准。

七、XCTest 框架中也是有 UI 测试的

例如: 创建一个 UI Test Target

image.png
image.png

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

生成文件示例


image.png

运行查看启动时长

image.png

启动时长日志输出

image.png

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

遇到的 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

image.png
image.png

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容