Swift中的单元和集成测试技术

单元测试 是软件测试的级别,其中测试软件的各个单元组件。 目的是验证软件的每个单元是否按设计执行。

集成测试 是一个软件测试级别,其中各个单元组合在一起并作为一组进行测试 。 此测试级别的目的是暴露集成单元之间交互中的错误。

测试网络请求

让我们看一个示例,该示例显示了使用UrlSession简单网络请求,如图1所示。 这些是我们在此代码中执行的任务

  1. 创建一个URL以获取资源
  2. 使用默认configuration创建一个dataTask对象
  3. 解码从服务器获取的响应

图2显示了符合Codable协议的响应模型struct ,否则.decode将引发error

如上图1所示,我们有三个任务单元。 通过将这两种方法组合在一起,我删除了此代码的可测试性。 我们可以打破这些任务,如图3所示

  1. 首先,我们将url请求创建逻辑分离到单独的单元,以便可以分别测试
  2. 其次,我们将解析逻辑分离到一个单元,以便该单元可以独立测试

单元测试请求的创建和解析

在这里,我们仅通过制作示例并放入url,将其传递到方法中,并对其返回值进行断言来测试makeRequest方法。 同样,我们可以通过传递一些模拟JSON并对所解析的结果进行断言来测试响应解析。 为了避免do catch块,我们使用了XCTest throws功能

集成测试网络电话

在进行任何集成测试之前,我们首先使逻辑通用。 正如我们所看到的,我们的网络api类仅特定于一个url,并且我们可以使用通用功能来消耗任何种类的url和响应负载

如您在图5中看到的,我们创建了一个APIRequest协议

如图6所示,我们创建了使用请求类型和urlSession实例初始化的APIRequestLoader类。 由于apiRequest应该符合APIRequest协议,因此我们可以访问makeRequestparseResponse方法。

如图7所示, GitHubRequest实现了APIRequest协议的APIRequestparseResponse方法,我们将RequestDataTypeString并将ResponseDataType设置为MyGitHub类型

如图8所示,我们正在使用通用api函数。 如果将其与图1进行比较,将会看到不同之处。

URLSessionDataTask集成测试

如图9所示,我们通过按正常调用方式调用loadAPIRequest方法来测试它,并等待返回结果,但这两者都要求我们的测试必须通过Internet连接运行

我们需要使加载程序类使用伪造的urlSession ,该伪造的urlSession返回一些模拟数据,而不是访问服务器,这也可以提高测试执行urlSession 。 所以问题是我们如何制作假的或模拟的服务器

使用URLProtocol模拟

URLSession为应用程序提供了高级API,以用于执行网络请求。 表示正在进行的请求的对象,例如URLSession数据测试。 不过,在幕后,还有另一个较低级别的API URLProtocol ,它执行打开网络连接,编写请求以及回读响应的基础工作。 URLProtocol旨在进行子类化,从而为URL加载系统提供扩展点。

Foundation为HTTPS等常见协议提供了内置协议子类,但是我们可以在测试中通过提供一个模拟协议来覆盖这些子类,该协议可以让我们对即将出现的请求进行断言并提供模拟响应。

URLProtocol通过URLProtocolClient协议将进度传达回系统。

如图10所示,我们使requestHandler成为静态属性。 我们在此物业网络中提供的内容将作为响应返回。 当我们调用resume方法时, startloading函数将调用,而不是去服务器,它立即返回requestHandler响应。 当我们在dataTask上调用cancelstoploading方法将调用。

测试配置

如图11所示,我们执行了许多步骤来配置伪造的urlSession对象

  1. 声明了apiRequestLoader对象类型,具有隐式拆包可选
  2. 创建了GitHubRequest对象来确定请求和响应类型,并具有创建和解析请求的逻辑
  3. 使用临时创建的会话配置对象,以避免将缓存,cookie或凭据写入磁盘
  4. 告诉Url会话在请求网络通话时使用MockURLProtocol 。 每次URL加载系统收到加载URL的请求时,都会搜索注册的协议处理程序来处理该请求。 每个处理程序通过canInitWithRequest (_ :)方法告诉系统是否可以处理给定的请求。
  5. 使用上述配置创建了一个URL会话对象
  6. 使用模拟会话和github请求创建了apiRequestLoader对象

如图12所示,这些是将要执行的步骤

  1. 创建mockJSONData json转换为data对象的mockJSONData变量
  2. 定义静态变量requestHandler关闭实现
  3. 创造expectations
  4. fulfill这些期望,测试方法将等待,直到满足所有期望或指定的超时时间为1秒。
  5. 调用loadAPIRequest方法,由于加载程序正在使用伪造会话,因此它要求dataTask从服务器获取数据,然后dataTask搜索自定义MockProtocol,以来要调用的协议MockProtocol,它要求MockProtocol处理该请求,并且MockProtocol发送requestHandler静态属性作为响应。

在图12中,我们使用的UrlSession技术特定于UrlSession 。 快速模拟提供了部分和完全两种口味。

URLSessionDataTask部分模拟

部分模拟,您正在修改现有类型,使其在测试中仅部分表现出不同

通常,当我们要从服务器获取数据时,我们在UrlSession上调用dataTask方法,该方法提供UrlSessionDataTask对象,而在调用UrlSessionDataTask对象的resume ,实际上会命中服务器。

如图13所示,我们创建了两个模拟子类。

MockUrlSessionMockUrlSession的子类。 我们在新的模拟类上添加了三个额外的属性,这些属性是在创建该模拟类时设置的。 该类还重写dataTask方法。 在dataTask重写的方法上,我们正在创建MockURLSessionDataTask对象,并将其传递给完成闭包

MockURLSessionDataTaskMockURLSessionDataTask的子类。我们在新的模拟类上添加了一个额外的属性,该属性在调用MockUrlSession覆盖的dataTask方法时设置。 在恢复方法时,将执行关闭方法,该方法会将模拟数据返回到MockURLSessionDataTask完成块。

如图14所示,这些是将要执行的步骤

  1. 创建了Github请求对象
  2. 创建了一个模拟会话
  3. 创建jsonData变量,并将json转换为data对象,并将其分配给mockSessiondata属性
  4. 使用模拟会话和github请求创建了apiLoader对象
  5. 创造expectation
  6. 调用loadAPIRequest方法,因为加载器正在使用模拟会话, MockUrlSession覆盖的datatask方法将执行,并将返回MockURLSessionDataTask 。 当loadAPIRequest调用resume() MockURLSessionDataTask将从覆盖的datatask方法的完成块返回我们在步骤3中创建的数据。

当您看到带有重写方法的模拟子类时。 您正在做部分嘲笑
部分模拟=子类+重写方法

部分模拟的问题

他们在WWDC中说

  1. SDK中的某些类不是为子类设计的,其行为可能有所不同。
  2. 我们只是部分修改对象。 这意味着我们对对象内部的工作方式都做出了非常困难的假设。

URLSessionDataTask完成模拟

完整的模拟:您将替换整个实现

URLSession情况下,我们将用模拟实现完全替换我们的依赖关系,因为我们需要扩展我们的依赖关系,即URLSession.

在完整的模拟中,我们使用协议来完全替代我们的依赖关系。 众所周知,在我们的方法中,我们只有UrlSession依赖项,可以从服务器获取数据。 如图15所示,我们创建了NetworkingSession协议,它将完全取代实现

如图16所示,我们通过使用扩展使UrlSession符合。 现在,如果要从服务器实际获取数据,则需要在UrlSession调用loadData而不是dataTask方法

如图17所示,我们创建了MockNetworkSession类,该类将用作模拟依赖项注入到我们的方法中,该方法将返回我们提供的数据,而不是直接到达实际服务器

我们在APIRequestLoader类中做了一些更改

  1. 首先,我们将urlSession属性的类型从UrlSessionNetworkingSession协议. 可以将符合NetworkingSession协议的任何对象分配给此属性。
  2. 我们使用协议方法加载数据。 如果注入MockNetworkSession类,它将返回模拟数据;如果传递URLSession ,它将命中服务器

如图19所示,这些是将要执行的步骤

  1. 创建了Github请求对象
  2. 创建了一个符合NetworkingSession协议的模拟会话
  3. 创建jsonData变量,并将json转换为data对象,并将其分配给mockSessiondata属性
  4. 创建的apiLoader对象的会话符合NetworkingSession和github请求
  5. 创造expectation
  6. 调用loadAPIRequest方法,由于加载程序正在使用模拟会话,因此MockUrlSession loadData方法将执行,并将从MockUrlSession对象的loadData方法的完成块返回我们在步骤3中创建的数据。

下一个

我们将看到如何使用上述模拟技术对CoreLocation进行单元测试

有用的链接

https://www.swiftbysundell.com/posts/mocking-in-swift
https://developer.apple.com/videos/play/wwdc2018/417/?time=623

如何轻松测试iOS联网代码
普遍认为,单元测试应该是第一位的:快速,隔离,可重复,自验证和及时。 可悲的是…… www.hackingwithswift.com 在Swift中使用NSURLProtocol
在本NSURLProtocol教程中,您将学习如何使用URL加载系统和URL方案来添加自定义… www.raywenderlich.com

https://www.hackingwithswift.com/articles/153/how-to-test-ios-networking-code-the-easy-way