Swift中XCTest和XCUITest的网络存根选项

原始文章:原始文章已发布在我的个人博客XCBlog上,继续阅读 此处 以获得更好的图形。 要阅读有关iOS DevOps和iOS CI / CD的更多有趣文章,请 在此处 访问我的博客 不要害怕没有广告!

每个iOS应用程序都要求数据显示在应用程序中。 不幸的是,我们无法将所有数据放入我们的iOS应用程序中。 iOS开发人员必须提出网络请求,才能从互联网获取此数据并在应用程序中使用它。 苹果提供了各种库和框架(例如URLSession,NSURLProtocol)来处理网络层,并且有一些第三方框架(如Alamofire)可用于处理网络请求。 但是,由于在测试网络层时使用了不同的方法,因此以网络请求形式测试异步代码变得非常复杂。 在这篇详细的文章中,我们将探索在Swift中测试网络层的所有可用选项,并涵盖可与XCTest框架一起使用的网络存根库的详细信息。

Swift中的网络测试

使用Swift编写的iOS应用的网络测试可以根据项目需求在单元,集成或UI级别执行。 单元和集成测试通常是快速而稳定的,而UI测试则是缓慢而脆弱的。 根据我在互联网上阅读的内容,人们正在使用各种方法在iOS中进行网络测试,其中一些常见方法如下

  • 在单元测试或集成测试中使用协议模拟类

模拟是脆弱而艰苦的,到Swift时模拟变得更加困难。 Swift中没有可用的成熟模拟库来生成类似Java,Ruby,Python或其他语言的模拟。 开发人员必须手动编写所有模拟,并将测试代码与生产代码紧密耦合。 这里有一篇很棒的关于iOS网络测试的文章,以了解有关如何使用协议进行模拟的更多信息。 这种方法无疑使该应用程序更具可测试性,但是它涉及编写很多协议和模拟类。 在Swift中,我们需要模拟我们需要测试的大多数类和方法。 对于iOS开发人员而言,这将是繁重的工作,很快便陷入混乱。

  • 记录和播放网络请求

Swift具有一些库,这些库使我们能够记录网络请求并进行回放,从而避免单元测试通过网络层。 它们将记录的数据存储在文件中,并且该数据被重用而不是进行网络调用。 最受欢迎的库是DVR,可对iOS应用发出虚假的NSURLSession请求。 还有另一个库Szimpla可以执行类似的操作。 您可以在这里在Szimpla上观看会议,在Realm学院中在DVR上观看会议。

  • 使用库存根网络请求

网络层还有另一种方法,它是进行网络调用而不是模拟URLSession并返回存根或静态响应而不是真实响应。 使用存根,我们仍然可以实现网络测试的目标,并且不必将测试代码与生产代码紧密耦合。 我们将看到可用于通过Swift存根网络请求的库的详细信息。 一些最受欢迎的库是用于XCTest(unit)测试的OHHTTPStubs,Mockingjay,Hippolyte。

  • 使用XCUITest框架与UI进行交互

用户界面测试贯穿网络层,涵盖了网络测试的所有方面。 苹果有XCUITest框架来涵盖Xcode UI测试。 我们可以使用一些库来为UITest存根网络。 一些流行的库是Swifter,SBTUITestTunel和XCUITest(UITest)的Embassy。

上面提到的所有方法都各有利弊,因此选择适合项目需求的方法至关重要。 在这篇文章中,我们将看到如何为单元和用户界面测试配置网络层。

Git定位应用

我们将在整个演示中使用GitHub API。 我们将向URL https://api.github.com/users/shashikant86发出网络请求,并从API获取位置并显示在应用程序中。 您可以通过在浏览器中访问JSON响应来查看它。 我们的应用程序具有一个按钮“ Make Network Request”,一旦按下它就显示来自API或存根数据的位置。 如果您此时不关注,请不要担心,我们将在本文结尾处提供包含所有测试的应用程序源代码。

存根单元测试(XCTest)

在深入探讨存根之前,让我们看看如何通过使用XCTest Framework进行真正的API调用来测试Github位置。 理想情况下,我们可以在应用程序中使用API​​客户端,然后将URL传递给客户端,并断言我们获取了正确的数据,但是我是懒惰的开发人员,根本没有对我的代码进行测试。 在这种情况下,我们必须从测试中进行直接网络调用。 典型的测试如下所示:

  func testGitUserData(){ 
卫队让gitUrl = URL(string:“ https://api.github.com/users/shashikant86”)否则{返回}
让诺言=期望(描述:“简单请求”)
URLSession.shared.dataTask(with:gitUrl){(数据,响应
,错误)
守护让数据=数据其他{返回}
做{
让json =试试JSONSerialization.jsonObject(with:data,options:JSONSerialization.ReadingOptions.mutableContainers)
如果让result = json为? NSDictionary {
XCTAssertTrue(result [“ name”] as!String ==“ Shashikant”)
XCTAssertTrue(result [“ location”] as!String ==“伦敦”)
promise.fulfill()
}
}抓住let er {
print(“ Err”,err)
}
}。恢复()
waitForExpectations(超时:5,处理程序:无)
}

在上面的测试中,我们使用URLSession进行网络调用并解析JSON响应以检查名称和位置字段。 该测试将通过,但没有什么缺点。 测试结果取决于网络,如果网络断开,退出测试将失败。 该测试将发出真正的网络请求并等待响应,因此它将非常慢。 这些测试将非常脆弱,脆弱且难以维护。 理想情况下,我们应该能够对响应进行存根并使其与测试网络相关。 对网络呼叫进行存根将使我们能够测试极端情况。 使用XCTest框架可以很容易地在单元级别对网络请求进行存根。 这可以通过我们将简要介绍的一些开源库来实现。

莫金杰

Mockingjay是使我们能够对网络调用进行存根并返回我们期望的响应的库。 可以使用Cocoapods安装。 该项目的自述文件中未提及安装迦太基,因此使用Cocoapods是个好主意。 我们可以对URL进行存根以响应所需的JSON数据。 例如,如果我们希望位置为巴黎,则可以使用Mockingjay像这样对响应进行存根:

  let body = [“ location”:“ Paris”] 
stub(uri(“ https://api.github.com/users/shashikant86”),json(body))

现在,Github API将以巴黎而不是伦敦的位置作为响应。 我们可以断言上述测试中的位置为XCTAssertTrue(result [“ location”] as!String ==“ Paris”)我们还可以在测试目标中创建一个JSON文件, 例如Feed.json存入静态JSON的响应文件。

  let path = Bundle(for:type(of:self))。path(forResource:“ Feed”,ofType:“ json”)! 

让数据= NSData(contentsOfFile:路径)!

stub(uri(“ https://api.github.com/users/shashikant86”),jsonData(数据作为数据))

这样,我们可以使用Mockingjay对API响应进行存根。 在撰写本文时,Mockingjay适用于Swift 3.2项目,并且在Swift 4项目中存在一些问题。

河马

Hippolyte是另一个存根库,它是Swift中的Switten,用于存根请求和响应。 我们可以使用Cocoapods或Carthage安装Hippolyte。 这是存根请求和响应的更轻松,更轻松的方法。 然后,您向Hippolyte注册此存根,并通过调用start()方法告诉它拦截网络请求。 在上面的示例中,我们可以使用以下代码轻松对JSON文件中的数据进行存根

 卫队让gitUrl = URL(string:“ https://api.github.com/users/shashikant86”)否则{返回} 
var stub = StubRequest(方法:.GET,网址:gitUrl)
var response = StubResponse()
let path = Bundle(for:type(of:self))。path(forResource:“ Feed”,ofType:“ json”)!
让数据= NSData(contentsOfFile:路径)!
让正文=数据
response.body =正文作为数据
stub.response =响应
Hippolyte.shared.add(stubbedRequest:stub)
Hippolyte.shared.start()

现在,对Github API的响应将使用Feed.json文件中的存根调用,我们可以声明此文件中提到的位置。 Hippolyte是轻量级的,可以愉快地用于请求和响应的存根,但是在这里查看了Hippolyte在Github上提供的全部功能之后,它可能没有高级功能。

OHHTTPStubs

从Objective-C时代开始,OHHTTPStubs框架就存在了。 该框架用于对iOS应用程序的Objective-C存根进行存根。 尽管OHHTTPStubs的根源是Objective-C,但它也具有适用于Swift的包装器。 我们必须使用Cocoapods安装OHHTTPStubs,并且必须在Podfile中安装Objective-C和Swift版本,如下所示:

  pod'OHHTTPStubs / Swift' 
pod'OHHTTPStubs'

在上面的示例中,我们可以对请求进行存根并使用OHHTTPStubs从静态文件返回响应

  stub(isPath(“ https://api.github.com/users/shashikant86”)){请求 
让stubPath = OHPathForFile(“ Feed.json”,type(of:self))
返回fixture(filePath:stubPath !,标头:[“ Content-Type”:“ application / json”])
}

现在,对Git API的响应将使用Feed.json文件中的存根调用,我们可以声明此文件中提到的位置。 OHHTTPStubs是一个相当成熟的库,具有完整的功能集,但它最初是为Objective-C设计的。

存根UI测试(XCUITest)

Apple在WWDC 2015上宣布了Xcode的UI测试支持。UI测试被设计为完全黑盒,无需访问主应用程序代码和数据。 XCUITest使用两个独立的进程Test Runner和Target应用程序,测试运行器启动了目标应用程序,并使用UIKit内置的可访问性功能与在单独进程中运行的应用程序进行交互。 在嘲笑或测试UITest时,我们几乎没有选择

  • 模拟和通过发射参数/环境

这意味着您不能直接模拟API。 与代理应用程序通信的唯一方法是传递启动参数或启动环境。 我们可以传递模拟API并创建启动环境变量,然后将其传递给XCUITests,但这需要大量的工作。 代码用if-else语句括起来,以确定听起来不太好的构建配置。

  • 使用Web服务器模拟后端并返回响应

剩下的其他选择是对网络呼叫进行存根并从服务器获取所需的数据。 为此,我们需要模拟后端并指向我们的应用程序以使用这些端点。 问题是我们是否仍可以使用上面提到的存根服务进行单元测试。 我们仍然可以使用这些库来处理网络请求,但是有时它需要更改主应用程序的逻辑,并且需要重构某些东西以增加额外的工作。 我们需要在生产代码中添加很多测试代码。

好的方法是在本地或远程运行Web服务器以控制所需的数据。 另外,它应要求对应用程序代码进行较少的更改。 据我在互联网上读到的,有些公司遵循良好的方法进行存根UI测试。 下面提到的一些方法来自他们的经验,而我自己尝试了其中的一些方法。 我们可以通过多种方法实现对UI测试的网络请求的存根。 让我们简单地考虑一下它们。 您可能会注意到,存根UI测试可用的库与我们用于单元测试的库完全不同,因为要使这些库与UITest一起使用还需要进行额外的工作。

迅捷

Swifter是用Swift编写的HTTP引擎,目前可在同步样式下使用,但下一版本计划采用异步样式。 Swifter存根UI测试方法最初是在本文中详细发布的,因此我不应该重复该方法,但总而言之,Swifter会进行以下操作

  • 在测试过程中运行服务器,因此我们无需显式启动或停止服务器。
  • 与应用程序代码完全隔离,所有内容均位于UI测试目标中。
  • CI Server集成不需要其他设置
  • 在执行测试时,我们可以使用初始存根以及加载动态存根

我们唯一需要做的就是通过更改Info.plist文件将我们的应用程序指向localhost。 我们可以添加以下代码以启用本地网络

   NSAppTransportSecurity  

NSAllowsLocalNetworking


NSAllowsArbitraryLoads

可以使用Cocoapods,Carthage和Swift Package Manager下载Swifter。 同样,所有设置XCUITests的指令都在此处的博客文章中。

大使馆

Envoy遵循的另一种很好的方法是对XCUITests的网络请求进行存根。 他们写了一篇很棒的博客文章。 基本上,他们创建了两个库来处理UI测试的网络请求存根。 图书馆是大使馆和大使馆。 您需要阅读本文才能开始使用Embassy,该大使馆也适用于最新的Swift版本。 总之,使馆的做法是

  • 基于轻量级和异步事件循环的样式
  • 我们需要编写/复制一些设置代码才能开始测试
  • 定义路由器并提供您的存根响应

SBTUITestTunnel

SBTUITestTunnel是另一个库,可用于存根网络请求以及执行其他一些操作,例如与NSUserDefaults交互,从应用程序的沙箱下载文件/向其下载文件,监视网络调用,定义在应用程序目标中执行的自定义代码块。 有一篇有关如何使用SBTUITestTunnel进行网络存根的文章,以及带有设置和使用指南的README文件。 总之,SBTUITestTunnel需要完成以下工作。

  • SBTUITestTunnel需要安装服务器和客户端库,Server需要安装在主要目标上,而客户端需要安装在UITest目标上。
  • 在主要目标中,我们需要更改构建配置并添加预处理器宏,在AppDelegate中,我们需要卸下服务器以进行调试或测试配置
  • 在测试本身中,我们可以对网络调用进行打桩以获得所需的响应。

服务器端Swift框架:蒸气

服务器端Swift框架尚未投入生产,但我们可以将其用于存根XCUITest的目的。 我还没有在互联网上读过任何人做的书,但是它工作得很好。 市场上几乎没有可用的Server Side Swift框架,例如Perfect,Kitura,Zewo和Vapor,但在此演示中,我们将使用Vapor。 我可能会写有关该主题的详细文章,但现在,让我们简短地讨论一下。

如此处所述,使用Swift Package Manager获取Vapor框架。 当在main.swift文件中调用特定端点时,我们需要告诉Vapor返回所需的响应

 进口蒸气 
 让drop =试试Droplet() 
  drop.get(“ users / shashikant86”){要求 
var json = JSON()
尝试json.set(“ location”,“ Vapor”)
返回json
}
 尝试drop.run() 

现在,我们告诉服务器在调用API端点时将位置作为Vapor返回。 我们可以构建服务器并使用启动它

  $快速建立 
$ .build / debug / Server服务

现在,您应该看到在端口8080上运行的服务器提供了所需的响应。 我们可以像往常一样通过指向本地主机的启动环境编写测试。

非快速Web服务器

仍然有些人喜欢运行用其他疯狂的编程语言(例如Java,Ruby,NodeJS等)编写的Web服务器。有多种选择来运行非Swift Web服务器,最常见的是

  • Sinatra:基于Ruby的Web服务器
  • 模拟服务器:提供多种语言
  • Wire Mock:基于Java的模拟服务器
  • NodeJS:JavaScript服务器

聊够了! 给我看代码

在这一点上,如果您仍在阅读,您可能会想知道,这个人谈论了很多关于各种库的问题,但是他妈的代码在哪里。 如前所述,上面讨论的所有内容都有代码,除了非Swift Web服务器之外,我已经尝试了上面提到的每个框架。 源代码在Github上可用

Shashikant86 / SwiftStub-XCTest

SwiftyStub项目具有如上所述的GitHub位置应用程序,并使用上述所有库涵盖了单元和UI测试。 该应用程序将环境变量BASEURL传递给该方案,以便我们在进行UI测试时可以使用localhost覆盖它。 一些库与Swift 4不兼容,因此该项目是使用Swift 3.2构建的。

您可以自己尝试。 只需克隆仓库

  $ git clone https://github.com/Shashikant86/SwiftyStub-XCTest.git 
$ cd SwiftyStub-XCTest
$ open打开SwiftStubs.xcworkspace

在单元测试目标SwiftStubTest中,存在使用Mockingjay,Hippolyte和OHHTTPStubs的存根单元测试。 此外,还有进行真实网络请求的单元测试。 在UI测试目标SwiftStubUITest中,我们具有使用Swifter,Embassy和SBTUITestTunnel进行存根网络的UItest以及真实网络测试。 您可以使用Xcode菱形图标执行每个测试,也可以按“ CMD + U”执行整个测试套件。

UI测试使用的Vapor可能会失败,这是预期的。 为了执行此测试,我们需要构建服务器并启动服务

  $ cd SwiftStubsUITests / Server / 
$快速建立
$ .build / debug / Server服务

现在,我们可以在Xcode中执行Vapor UI测试。 请花点时间通过测试,并让我知道是否需要任何改进。

选哪一个?

正确的库的选择取决于团队和项目本身的工程师技能。 要检查的重要因素是Swift版本的兼容性

单元测试

如果团队使用了Objective-C旧版本的OHHTTPStubs,那么值得继续为Swift项目使用。 如果您只需要响应存根,那么像Hippolyte这样的轻量级产品就可以胜任。 Mockingjay也是一个很好的库,但是在写这篇文章的时候,它与Swift 4并没有按预期工作,因此值得在使用该库之前进行检查。

UI测试

我不推荐运行用Java或另一种疯狂的语言编写的非Swift服务器的方法,因为如果它由于未知原因停止工作,那将是混乱的。 在撰写这篇文章时,Swifter与Swift 4不兼容,而Github上的拉取请求是开放的。 在服务器作为测试过程的一部分启动和停止时,快速的方法看起来很轻松,但是它以同步方式工作。 值得一试的其他方法(例如Embassy和SBTUITestTunnel)还监视GitHub存储库,并查看它们是否仍在维护中。 最后,如果您超级勇敢,那么请转到服务器端Swift框架。 他们对这项工作有点沉重,但可以肯定的!

结论

在Swift中很难对网络层进行测试,因为它需要在整个应用程序中进行大量协议模拟和依赖注入代码。 通过使UI和单元测试独立于网络并完全控制数据,使用轻量级的第三方库对网络请求进行存根将使其更加简单。 您将XCTest与存根数据一起使用的经验是什么? 我错过了好礼吗? 您从这篇文章中使用或喜欢哪种方法? 请在评论中提及。

像XCBlog的 XCTEQ 发布的帖子一样 您可能还喜欢我们的一些服务,例如访客博客或Mobile DevOps(CI / CD)或测试自动化。 Github 搜索我们的 服务 ,开源项目, 或者在 Twitter Facebook Youtube LinkedIn 上关注我们 下载我们的 XCBlog iOS应用程序以离线阅读博客。

X CTEQ 一家专门从事基于Mobile DevOps,CI / CD,Mobile,AI / ML的测试自动化Checkout XCTEQ产品和服务的公司, 网址 http://www.xcteq.co.uk 或写信给我们info@xcteq.co。英国..