大多数浏览器和
Developer App 均支持流媒体播放。
-
测试技巧和窍门
测试是以一致方式验证代码能否正常工作的一种必备工具,但代码常常会涉及超出您控制范围内的依赖关系。探索相应的技巧,以便使用 XCTest 在 Apple 平台上顺利测试原本难以测试的代码。了解各种相关技巧,以便编写质量更高、运行快速且只需少量维护的测试。
资源
相关视频
WWDC22
WWDC20
-
下载
(测试提示和技巧 演讲417)
大家好 欢迎来到“测试提示和技巧” 我叫Brian Croom 我的同事Stuart和我 真的很高兴与你们分享 一些很棒的测试技术 我们最近一直在学习这些技术
随着会议的临近 我们觉得如果我们能有一个app 用它能够在会议中心周围 找一些我们能够去看和做的东西 这会是一件很酷的事情
我们一直在创建这app 为它在圣何塞周围寻找 各种兴趣点提供视图 并列出了它们离你有多远
现在当然我们想要确保 我们有一个非常棒的测试套件 我们可以运行这个app 让我们相信我们的代码能够正常工作 并且随着开发的继续 可以保证代码继续工作
今天我们想与你们分享四套 我们在为app编写测试时 发现的非常有用的技术
一些在app中测试网络代码的策略
一些用于处理基础通知对象的 测试技巧
在测试中使用模拟对象时 利用协议的方法 以及一些使测试运行速度 非常快的技术
现在让我们开始讨论网络吧
为了允许动态内容更新 我们一直在开发app 以便从远程网络服务器加载数据
这里我们发现一些 网络代码编写测试时有用的东西
首先快速回顾一下去年的情况 在2017年WWDC “可测试性工程”演讲中 我们讨论了金字塔模型 它是如何构造测试套件 平衡彻底性 可理解性和执行速度的指南
总之 理想的测试套件往往是 由大量集中的单元测试组成 能够在你的app中 执行单独的分类和方法
它们的特点是阅读简单 在检测到问题时 能产生明确的失败消息 并且运行速度非常快 通常是每分钟数百或数千次测试
这些测试由较小数量的 中型集成测试作为补充 这些测试以app中的 离散子系统或类集群为目标 检查它们是否一起正常工作 每个测试只需几秒钟即可运行
这个套件是一些端到端的系统测试 最常见的形式是UI测试 它非常类似于终端用户 在他们的设备上的操作方式
检查所有的部分 是否正确地连接在一起 以及检查与底层操作系统 和外部资源进行良好的交互
遵循该模型的测试套件 可以全面描述app代码的基本功能
为了测试这个app中的网络堆栈 我们真的很想 把这个金字塔模型放在心上 将它作为如何构建测试套件的指南
在这里我们看到了 在app中发出网络请求并将数据 输入UI所涉及的高级数据流
在这个app的早期原型中 我们在视图控制器中有个方法 它可以在一个地方完成所有这些工作 它看起来和这个很相似
该方法接受一个带有用户位置的参数
并使用该参数为我们的 服务器API端点构造URL 并将该位置作为查询参数
然后它使用Foundation的 URLSession API 为向那个URL得到一个请求 而创建一个数据任务
当服务器响应时它将打开数据
使用foundation的 JSONDecoder API对其解码 将其解码为一个有关值的数组 这是我在其他地方声明过的一个结构 并符合可解码协议
然后将其存储到属性中 以驱实现动表视图数据源 并将其放到屏幕上
现在非常值得注意的是 我能够 利用Swift 和Foundation的强大功能 能够在大约15行代码中 完成所有这一切
但是通过一个方法来做所有这些 那么我就损害了代码的可维护性 特别是代码的可测试性
来看看我们的测试金字塔的基础 我们真正想要做的是为每个流的 每个部分编写焦点单元测试
让我们首先考虑请求准备 和响应解析步骤
为了使这段代码更具可测试性 我们首先从视图控制器中取出它 并在这个专用的
PointsOfInterestRequest 类型上创建了两个方法
这给出了两个很好的解耦方法 每个都取一些值作为输入 并将它们转换为一些输出值 而不产生任何副作用
这使得我们很容易 为代码编写一个集中的单元测试
在这里我们测试 makeRequest方法 只要做一个样本并放置位置 将其传递到这个方法中 并对其返回值做出一些认定
类似地 我们可以通过传入 一些模拟JSON 并对解析的结果进行断言 来测试响应解析
关于这个测试的另一点需要注意的是 我正在使用XCTest支持 来用于标记为抛出的测试方法 这允许我在测试代码中使用try 而不需在测试代码周围使用 明确的do-catch块围绕它
现在让我们看看 与URL会话交互的代码
这里我们再次将其拉出视图控制器
并使用与我们刚才看到的 请求类型匹配方法签名的方法 创建了APIRequest协议
这是被请求类型初始化的 APIRequestLoader
和urlSession实例类 来使用的
这个类有一个 loadAPIRequest方法 它使用该apiRequest值 来生成一个URL请求 将其输入到urlSession中
然后再用apiRequest 在你的响应中进行解析
现在我们可以继续为这个方法 编写单元测试 但是现在我实际上想要向上 移动金字塔 并查看一个中级集成测试 它涵盖了这个数据流的几个部分
在套件的这一层我还能够进行的 另一个测试是 我与URLSession API 的交互是正确的
事实证明 foundation URL加载 系统为实现这点提供了很大的帮助
URLSession为用于 执行网络请求的app 提供了一个高级API
弯曲对象 如表示飞行请求的 URLSession数据任务
然而在幕后还有另一个较低级别的 API URLProtocol 它来执行打开网络连接 编写请求 和读取响应的底层工作
URLProtocol 被设计为子类 为URL加载系统提供了一个扩展点
Foundation为诸如 HTTPS这样的公共协议 提供内置协议子类 但是我们可以在测试中覆盖这些内容 方法是提供一个模拟协议 允许我们对即将发出的请求进行断言 并提供模拟响应
URLProtocol通过 URLProtocol客户端实例 将进程通信回系统
我们可以用这个方法
我们在测试包中创建一个 MockURLProtocol类 重写canInit请求 以向系统表明 我们对它提供的任何请求感兴趣
实现canonicalRequest 用于请求 但startLoading和StopLoading 方法用于大多数操作发生的地方
为了给我们的测试提供一种连接到 这个URLProtocol的方法 我们将提供一个闭包属性 requestHandler来设置测试
当一个URLSession 任务开始时 系统将实例化我们的 URLProtocol子类 为它提供URLRequest值和 URLProtocol客户端实例
然后它将调用 startLoading方法 在该方法中我们将使用测试设置的 requestHandler 并在参数处使用 URLRequest调用它
我们将获取它返回的内容 并将其作为URL响应 和数据传递回系统
或作为一个错误传递回系统
如果你想要取消测试请求 我们可以在停止加载方法实现中 执行类似的操作
有了存根协议我们就可以编写测试了
我们创建一个 APIRequestLoader实例 用一个请求类型和一个配置为使用 我们的URLProtocol的 URLSession来配置它
在测试体中我们在MockURLProtocol上 设置了一个requestHandler 对将要发出的请求作出断言
然后提供一个存根响应
然后我们可以调用 loadAPIRequest
等待调用完成块
并对解析响应作出断言
在这一层上的几个测试可以给 我们很大的信心 使我们相信我们的代码 能够很好地协同工作 而且我们正在与系统进行适当的集成 例如如果我忘记在我的数据任务中 调用简历的话 那么我们刚才看到的测试就会失败 我相信我不是唯一犯过这个错误的人
最后包含一些系统级的 端到端测试也是非常有价值的
实际上测试 UI测试是一个很好的工具
要了解更多关于UI测试的信息 请参考2015年WWDC的 “Xcode中的UI测试”演讲
现在当你开始编写真正的 端到端测试时 你遇到的一个重大挑战是 当发生问题时 当你遇到测试失败时 很难知道从哪里开始 寻找问题的根源
我们最近在测试中 帮助缓解这种情况的一件事是 设置一个模拟服务器的本地实例 中断我们的UI测试来对其发出请求 而不是对真正的服务器进行请求
这使得我们的UI测试更加可靠 因为我们可以控制 反馈到app中的数据
虽然在这个上下文中 使用模拟服务器是非常有用的 但是让一些测试针对真正的 服务器发出请求也是很好的
其一种很酷的技术就是 可以在单元测试包中进行一些测试 直接调用 在Stack中工作的app 并使用这些测试 将请求指向真正的服务器
这提供了一种验证服务器 接受请求的方式 是否与app的方式相同的方法 并且你可以解析服务器的响应 而不必同时处理 测试UI的复杂问题
因此最后我们看到了一个 将代码分解成更小独立的部分 以便单元测试的例子
我们已看到了如何使用URLProtocol 作为模拟网络请求的工具
我们还讨论了如何使用 金字塔的强大功能 来帮助我们构建 一个平衡良好的测试套件 这将使我们对我们的代码充满信心
现在我想把Stuart叫到台上 来谈谈更多的技术
谢谢
谢谢 Brian 那么我想要讨论的第一个领域是 测试通知的一些最佳实践方法
澄清一下 这里的通知 我说的是基础级别的通知 即NSNotification 和Objective-C 是的 有时我们需要测试 一个主题是否观察到一个通知 而另一些时候我们需要测试 一个主题是否发布了一个通知 通知是一种一对多的通信机制 这意味着在发布单个通知时 它可能会通过 你的app发送给多个收件人 甚至是你的app进程运行的 框架代码中 因此正因为这一点 我们必须始终 以独立的方式测试通知 以避免意外的副作用 因为这可能导致不稳定不可靠的测试 让我们看一个 有这个问题的代码的例子
在这里有 Brian和我 正在构建的app 的PointsOfInterest TableViewController 表视图中显示了附近有趣地点的列表 每当app的位置授权发生变化时 它可能需要重新加载数据 因此它从app的 CurrentLocationProvider类中 观察到一个名为 AuthChanged的通知 当它观察到这个通知时 它会在必要时重新加载它的数据 为了这个例子 它设置一个标志 这样我们的测试代码就可以检查标志 看看是否实际收到了通知 这里我们可以看到它正在使用 默认的通知中心来添加观察者
让我们来看看 这个代码的单元测试可能是什么样子
在这个类的测试中 我们发布了AuthChanged 方法通知来模拟它 并将它发布到默认的 NotificationCenter 与我们的视图控制器使用相同 现在这个测试起作用了 但它可能在app代码的 其他地方有未知的副作用 一些系统通知很常见 比如UI app appDidFinish Launchingnotification 被许多层观察到并且有未知的副作用 或者它只会减慢我们的测试速度 因此我们想 更好地隔离这些代码来测试它
我们可以使用一种技术 来更好地隔离这些测试 要使用它我们首先必须认识到 NotificationCenter 可以有多个实例 你可能会注意到 它有一个作为类属性的默认实例 但它支持在必要时创建其他实例 这将是隔离我们测试的关键 因此要应用这种技术我们首先 必须创建一个新的NotificationCenter 将它传递给我们的主题并使用它 而不是使用默认的实例 这通常被称为依赖注入 我们来看看 如何在视图控制器中使用它
在这里我有使用默认 NotificationCenter的原始代码 我将它修改为使用一个单独的实例
我添加了一个新的 NotificationCenter属性 并在设置它的初始化器中 添加了一个参数 它使用这个新属性 而不是向默认中心添加一个观察者
我还将向初始化器添加默认的 默认参数值 这样可以避免在我的app中 破坏任何现有代码 因为现有的客户端不需要通过 新的参数 只有我们的单元测试会需要
现在让我们回去并更新我们的测试
这是最初的测试代码 我已修改它以使用单独的 NotificationCenter
这显示了如何测试 我们的主题是否观察到了通知 但是如何测试 我们的主题是否发布了通知呢? 我们将再次使用相同的 单独NotificationCenter技巧 但我还将展示如何使用 内置的预期API 来添加通知观察者
这是我们app的另一部分代码 CurrentLocationProvider类 稍后我将详细讨论这个类 但是请注意 它有向我app中其他类 发送消息的方法 即该app的位置授权 已经通过发布通知进行更改
和我们的视图控制器一样 它目前正硬编码默认的 NotificationCenter
这是我为这个类写的单元测试 它用来验证 在调用NotifyAuthChanged 方法时是否发布通知
我们可以在这里的中间部分看到 这个测试使用addObserver方法 创建一个基于块的观察者 然后它移除块内的观察者 现在我可以对这个测试做一个改进 就是使用内置的 XCTNSNotificationExpectation API 来为我们创建这个 NotificationCenter观察者
这是一个很好的改进 它允许我们删除几行代码
但它仍然有我们之前看到的问题 即隐式使用默认 NotificationCenter 我们来解决这个问题
这是我们的原始代码
我将应用前面看到的相同的技术 在初始化器中使用一个单独的 NotificationCenter 存储它并使用它而不是默认值
现在回到我们的测试代码 我将修改它 将一个新的NotificationCenter 传递给我们的主题 但是现在我们来看一下期望 当我们的测试期望接收到 一个特定中心的通知时 我们可以将 NotificationCenter参数 传递给预期的初始化器
我还想指出这个期望的超时值是0 这是因为我们实际上期望 它在我们等待它时已实现 这是因为在 NotifyAuthChanged方法返回时 方法返回时通知应该已经发布了
因此使用这对技术来测试通知 我们可以确保我们的测试 保持完全隔离 并且我们在不需要修改app中的 现有代码的情况下进行了更改 因为我们指定了默认的参数值
接下来我想谈谈在编写单元 测试时经常遇到的一个挑战 即与外部类交互
在开发app时 你可能会遇到这样的情况 你的类正在与其他类对话 无论是在app的其他地方 或者还是由SDK提供的 你发现编写测试很困难 因为创建外部类很难甚至是不可能的 这种情况经常发生 特别是 对于那些没有被直接创建的API中 而且当这些API具有需要测试的 委托方法时就更加困难了
我想展示如何通过模拟 与外部类的交互来使用协议 进而解决这个问题 而且这样做并不会降低测试的可靠性
在我们的app中 我们有一个使用CoreLocation的 CurrentLocationProvider类 它创建一个 CLLocationManager 并在它的初始化器中配置它 设置它想要的精度属性 并将自己设置为委托
这是这门类的重点 它是一个名为 checkCurrentLocation的方法 它请求当前位置并获取一个完成块 该块返回该位置是否为感兴趣的点
请注意我们在这里调用 CLLocationManager 上的请求位置方法 当我们调用它时 它将尝试获取当前位置 并最终调用类上的委托方法 那么 让我们来看看委托方法
我们使用一个扩展来遵循这里的 CLLocationManagerDelegate协议 我们并且调用一个存储完成块 好的 让我们为这个类写一个单元测试
这里是我尝试编写的一个 如果我们阅读它 我们可以看到它首先创建一个 CurrentLocationProvider 然后检查所需的精度 以及委托是否设置好 到现在为止还好 但事情就变得棘手了 我们想测试 checkCurrentLocation方法 因为这是我们的主要逻辑所在 但是我们有一个问题 我们无法知道何时调用请求位置方法 因为这是CLLocationManager上的 一个方法而非代码的一部分
我们在这个测试中 可能会遇到的另一个问题是 CoreLocation 需要用户授权 如果之前没有授权 它会在设备上显示权限对话框 这导致我们的测试依赖于设备状态 并且使它们更难维护 最终更有可能失败
因此如果你在过去遇到过这个问题 你可能会考虑对外部类进行子类化 并重写你调用它的任何方法 例如我们可以在这里尝试子类 CLLocationManager 并重写RequestLocation方法 这在一开始可能行得通 但有风险 SDK中的一些类 不是被设计成子类的 它们的行为可能不同 另外我们仍然需要调用 超类的初始化程序 这不是我们可以重写的代码 但是主要的问题是 如果我修改代码来调用 CLLocationManager上的另一个方法 我也要必须记住 在我的测试子类上重写该方法 如果我依赖子类 编译器不会通知我 我已经开始调用另一个方法 这很容易忘记和破坏我的测试 因此我不推荐这种方法 而我推荐使用协议模拟外部类型 我们来看看怎么做
这是原始代码 第一步是定义一个新协议
我已经命名了新协议 LocationFetcher 它包含了我的代码 从CLLocationManager中 使用的方法和属性的确切集合 成员名称和类型完全匹配 这允许我在 CLLocationManager上 创建一个符合协议的空扩展 因为它已经满足了所有要求
然后我将LocationManager属性 重命名为LocationFetcher 并将其类型更改为 LocationFetcher协议
我还将向初始化器添加一个 默认参数值 就像我之前所做的那样 以避免破坏任何现有的app代码
我需要对checkCurrentLocation 方法做一个小的更改 以使用重命名的属性
最后让我们看一下委托方法 这个部分处理起来有点棘手 因为委托期望Manager参数 是真正的CLLocationManager 而不是我的新协议 因此当委托参与时 事情会变得有点复杂 但是我们仍然可以在这里应用协议 让我们来看看
我将回到前面定义的 LocationFetcher协议 并将该委托属性重命名为 LocationFetcherDelegate 我将其类型更改为一个新协议 该协议的接口与 CLLocationManagerDelegate几乎相同 但是我调整了方法名 我将第一个参数的类型改为 LocationFetcher
现在我需要在扩展中实现 LocationFetcherDelegate属性 因为它不再满足这个要求 我将实现getter 和setter来使用强制转换 来回转换到 CLLocationManagerDelegate 稍后我会解释为什么 我在这里使用强制转换
然后在我的类的初始化器中 我需要用locationFetcher Delegate替换委托属性
最后一步是修改原来的扩展 以符合新的模拟委托协议 这部分很容易 我需要做的就是替换协议和方法签名 但是 我实际上仍然需要遵守旧的 CLLocationManagerDelegate 协议 这是因为真正的 CLLocationManager 不知道我的模拟委托协议
因此这里的诀窍是 将符合实际委托协议的 扩展添加回来 但让它调用上面等效的 locationFetcher委托方法
前面我提到我在委托 getter和setter中 使用了强制转换 这是为了确保我的类符合这两种协议 我没有忘记其中的一种或另一种
因此在我的单元测试中 我将为模拟定义一个嵌套在 我的测试类中的struct 它符合 locationFetcher协议 并满足其要求 注意在它的requestLocation方法中 它调用一个块来获取一个假的位置 我可以在测试中自定义它 然它后调用委托方法 将那个假位置传递给它
现在我有了我所需要的一切 我可以写我的测试 我创建一个 MockLocationFetcher结构 并配置它的 handleRequestLocation块 以提供假位置
然后我创建CurrentLocationProvider 并将MockLocationFetcher传递给它 最后我用一个完成块调用 checkCurrentLocation 在完成块中有一个断言 用于检查位置 是一个感兴趣的点
所以它很管用 我现在可以模拟我的类使用 CLLocationManager 而不需要创建一个真正的 CLLocationManager
因此在这里我展示了如何使用 协议来模拟与外部类及其委托的交互 这是很多步骤 让我们回顾一下我们所做的
首先我们定义了一个新的协议 表示外部类的接口 该协议需要包含 我们在外部类上使用的 所有方法和属性 并且通常它们的声明可以完全匹配
接下来我们在原始的 外部类上创建了一个扩展 它声明符合协议
然后我们用我们的新协议替换了 外部类的所有用法 并添加了一个初始化器参数 以便在测试中设置该类型
我们还讨论了如何模拟委托协议 这是SDK中常见的模式
这里还有一些步骤 但这里是我们所做的
首先我们定义了一个模拟委托协议 其方法签名 与我们正模拟的协议相似 但是我们用模拟协议类型 代替了真正的类型
然后在我们最初的模拟协议中 我们重新命名了委托属性 并在扩展中实现了该重命名属性
因此虽然这种方法 可能需要更多的代码 而不是子类之类的替代方法 ,它会更可靠 更不可能打破 我扩大我的代码 因为这样编译器将强制执行 我为代码调用的任何新方法 必须包括在这些新协议中
最后我想谈谈测试执行速度
当你的测试需要很长时间运行时 你就不太可能在开发期间运行它们 或者你可能会尝试跳过 运行时间最长的测试 我们的测试套件 会帮助我们及早发现问题 而修复回归是最容易的 因此我们希望确保我们的测试 总是尽可能快地运行
现在你可能在过去遇到过一些 需要在测试中人工等待或休眠的情况 因为你的测试是异步的 或者使用了计时器 延迟操作是很棘手的 我们希望确保 在我们的测试中包含它们 但是如果我们不小心的话 它们也会使事情变慢很多 因此我想谈谈一些 我们可以避免 在我们的测试中人为延迟的方法 因为它们永远都是不必要的
下面是一个例子 在Brian和我正在构建的 兴趣点app上 在主UI上 我们在底部有一个条带 它显示了特色的地方 它基本上是绕着附近的顶部旋转 每隔十秒钟就会显示一个新的位置 现在有几种方法可以实现这个功能 但是这里我使用的是 foundation中的timer API
让我们看一下我可能 为这个类编写的单元测试 它创建一个 FeaturedPlaceManager 并在调用scheduleNextPlace方法之前 存储它的当前位置 然后运行循环11秒 我加了一秒钟作为宽限期 最后它检查当前位置 在最后发生了变化 现在这不是很好 它需要很长时间才能运行 为了缓解这一问题 我们可以在代码中公开一个属性 以允许我们将超时定制为更短的时间 比如一秒 这就是代码改变的样子
现在使用这种方法 我们可以将测试延迟减少到1秒 这个方法比以前的好 我们的测试肯定会运行得更快 但仍然不理想 我们的代码还有延迟只是时间更短 真正的问题是 我们正在测试的代码仍然依赖于时间 这意味着 当我们使预期的延迟越来越短 我们的测试可能变得不那么可靠 因为它们将更依赖于CPU 来预测事情的进度 这并不总是正确的 尤其是对于异步代码 那么让我们来看看一个更好的方法
我建议首先确定延迟机制 在我的示例中它是一个定时器 但你也可以使用DispatchQueue 的asyncAfter API
我们想要模拟这个机制 以便我们能够 立即调用延迟的操作并绕过延迟
这里是我们的原始代码 让我们从这个scheduledTimer方法的 实际功能开始
ScheduledTimer 方法实际上为我们做了两件事 它创建一个计时器 然后将 该计时器添加到当前运行循环中 现在这个API 可以非常方便地创建计时器 但是如果我将这两个步骤分开 它将帮助我们使代码更可测试
在这里 我将前面的代码从使用 scheduledTimer转换为先创建计时器 后将其添加到当前的runLoop秒中 我已经将其存储在一个新属性中
现在这段代码相当于我们以前的代码 但是一旦我们将这两个步骤分开 我们就可以看到runLoop 只是这个类与之交互的另一个外部类 因此我们可以应用 我们前面讨论过的 带有协议的模拟技术 为此我们将创建一个小协议 其中包含这个addTimer方法
我调用了该新协议 TimerScheduler 它只有一个addTimer方法 它与runLoop API 的签名相匹配
现在回到我的代码中 我需用刚刚创建的协议 替换runLoop
在我的测试中我不想使用真正的 runLoop作为我的TimerScheduler 相反我想创建一个模拟调度器 它将计时器传递给我的测试
为此 我将创建一个嵌套在 我的单元测试类MockTimerScheduler中的新结构 它符合TimerScheduler协议 它存储一个块 每当被告知要 添加计时器时 该块就会被调用
有了所有的片段 我就可以写我的最后单元测试了 首先我创建一个MockTimerScheduler 并配置它的handleAddTimer块 这个块接收计时器 一旦它被添加到调度程序中 它将记录计时器的延迟 然后通过触发计时器来绕过延迟 来调用块
然后我们创建一个FeaturedPlaceManager 并为它提供MockTimerScheduler
最后 我们调用scheduleNextPlace 开始测试 好啊 我们的测试不再有任何延迟 它们执行得非常快而且不依赖于时间 所以它会更可靠 作为奖励 我现在可以使用底部的这个断言 来验证计时器延迟的数量 这不是我在之前的测试中所能做
所以就像我说的 我们代码的延迟已经完全消除了 我们认为这是测试涉及 延迟操作代码的一种很好的方法 但是对于测试中最快的总体执行速度 最好是直接构建大部分测试 而根本不需要模拟延迟操作
例如 在我们的app中 被延迟的动作 正在更改到下一个功能位置 我可能只需要一两次测试 就可以证明计时器延迟正常工作 对于其余的类 我可以直接调用 ShowNextPlace方法 根本不需要模拟一个计时器调度程序
在我们讨论测试执行速度这一主题时 我们还有一些其他的技巧要分享
我们已经看到的一个领域是 NSPredicateExpections的使用 我们指出的是这些类 几乎不像其他预期类那样具有性能 因为它们依赖于轮询机制 而不是更直接的回调机制 它们主要用于UI测试 其中评估的条件发生在另一个进程中 因此在单元测试中 我们建议使用更直接的机制 例如常规的XCTestExec NSNotification或KVOExec
另一个测试速度提示是 确保你的app尽快启动 现在大多数app都必须 在启动时做一些设置工作 尽管这种工作 对于常规app的启动是必要的 但当你的app作为 测试运行程序启动时 很多工作可能是不必要的 例如加载视图控制器 启动网络请求 或配置分析包 这些都是单元测试场景中 通常不必要的东西
XCTest在开始运行测试之前 等待app委托完成启动方法返回 因此若你分析并注意到 app启动在测试中 花费了很长时间 一个技巧就是检测你的app 何时作为测试运行程序启动 并避免这项工作
一种方法是指定自定义环境变量 或启动参数 打开方案编辑器 转到左侧的测试操作 然后到参数选项卡 然后添加一个环境变量 或一个启动参数 在这个屏幕截图中 我添加了一个环境变量 名为IS-UNIT-TESTING 设置为1
然后 修改app委托的 appDidFinishLaunching代码 使用类似于此的代码检查此条件 现在 如果你这样做 请确保你跳过的代码 对于单元测试的功能来说并不重要
最后总结一下Brian开始时候 提醒我们测试金字塔 以及如何在app中 有一个均衡的测试策略 展示几种测试网络操作的实用技术
然后我讨论了隔离基础通知 并使用依赖项注入
我们为编写测试时最常见的挑战之一 即与外部类交互 提供了一个解决方案 即使它们有一个委托
我们还分享了一些保持测试快速运行 和避免人为延迟的技巧 我们真的希望你觉得这些测试有用 并在下次编写测试时 寻找应用它们的方法
要了解更多信息 请查看这个链接上的演讲网页 如果你错过了 我们希望你能看看 周三“视频测试新特性”演讲 非常感谢 希望你们度过一个很棒的WWDC
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。