如何使用FBSnapshotTestCase在iOS中进行UI测试

在视图控制器中更改某些代码已经发生了多少次,并且在生产环境中测试应用程序时,视图已经发生了神奇的变化? 如果我们不想,我们是否应该有某种方法来确保视图不变?

因此,进行UI测试很重要。 通过添加UI测试,我们可以确保不会对UI进行意外更改。

在本文中,我们将在一个简单的示例项目中专注于FBSnapshotTestCase的用法。

FBSnapshotTestCase的历史

最初,FBSnapshotTestCase由Facebook创建和维护。 这是一个非常有用的工具,具有非常简单的实现。 所需要做的就是将库集成到项目中,并实例化我们要测试的UIViewUIViewController子类。

不幸的是,自去年以来,Facebook已停止维护该工具。 Facebook已经创建了另一个与其内部基础设施紧密相关的工具,因此他们不赞成该工具。

对我们来说幸运的是, Uber决定为他们当前维护的项目创建一个分支。

FBSnapshotTestCase测试实际上是单元测试,它使我们能够将UIViewController的参考图像(也可以是UIView,但通常是在全屏模式下)与视图的当前呈现进行比较。 如果存在任何不一致(第一张图像与第二张图像之间存在差异),FBSnapshotTestCase将生成第三张图像,并以灰度标出差异。 如果参考图像和项目中的电流之间存在差异,则该工具将生成此第三张图像,并且在该单元测试中会生成错误,从而导致测试套件产生故障。

Fastlane为我们提供了类似的工具Fastlane Snapshot(存储库),它使我们能够在Xcode UI测试中截取屏幕截图。 所有UI测试完成后,如果它们都获得批准,它将生成带有所有屏幕截图的HTML文件。 Fastlane Snapshot默认情况下还允许我们设置我们要查看的所有设备和语言。 这对于测试所有可能的组合非常有用(如果您有3种语言,应用程序中有10个屏幕,并且支持5种设备,则它会生成3 x 10 x 5 = 150个屏幕截图!)。 问题在于,这只是一个屏幕截图,它没有为我们带来有关图像是否具有正确UI的信息。

总而言之,FBSnapshotTestCase允许我们比较并告诉我们是否有任何错误,Fastlane Snapshot只是为我们带来了Xcode UI测试的屏幕截图。

我们将讨论以下主题,力图充分利用该工具的潜力:

  • 如何集成FBSnapshotTestCase
  • 使用SnapKit重构代码并使用FBSnapshotTestCase
  • 编写脚本以生成具有不同分辨率的屏幕

要开始本教程,请下载以下示例项目https://github.com/fedejordan/FBSnapshotTestCaseExample

在模拟器中编译项目(我使用过iPhone 8),您将看到以下屏幕:

一个简单的View Controller,可以是任何应用程序的第一个屏幕,带有登录和注册按钮。

我已经使用CocoaPods集成了SDK。 您可以使用Carthage或您选择的包裹管理员。 集成该工具的步骤在存储库指南中。 为了方便起见,以下是使用CocoaPods的安装说明:

  1. 在终端应用程序中,我们输入pod init以创建podfile
  2. 在podfile中,我们添加pod 'FBSnapshotTestCase'
  3. 我们在终端中进行pod install以下载SDK

我在这里使用的版本是2.1.6

将SDK集成到项目中后,我们将打开CocoaPods生成的工作区并编译项目,以确保一切正常。

就像它在FBSnapshotTestCase官方指南中所说的那样,我们转到Edit Scheme -> Run configuration ,以正确设置参考和失败测试图像的文件夹(与参考图像不同的文件夹)。 我们设置相应的环境变量。

现在,我们可以使用FBSnapshotTestCase创建我们的第一个单元测试。 为此,我们必须执行以下操作:

  1. 我们创建一个新的单元测试,并子类FBSnapshotTestCase而不是XCTestCase
  2. 在测试内部,我们使用FBSnapshotTestCase告诉我们要截屏的视图。 稍后,我们使用UIStoryboard类实例化ViewController类。
  3. 在测试类内部,使用setUp方法中的self.recordMode = YES运行测试。 这将在磁盘中创建参考映像。 每当我们想要在屏幕上进行有意更改时,我们都必须这样做。
  4. 删除启用记录模式的行,然后再次运行测试。

我们的代码应与此非常相似:

每当我们在UI中添加或修改内容时,都将使用此图像与项目生成的未来图像进行比较。

如果要查看测试结果,可以在我之前告诉过的同一存储库中对分支first_snapshot_test

让我们看一下该工具可以带给我们的实际用法。

假设我开始在一个团队中工作,并且我决定故事板不是可伸缩的,所以我决定将所有UI内容迁移到代码中。 我进行了一些调查,并且喜欢一个名为SnapKit的库。

我不会谈论如何使用该库的细节。 因此,我将直接显示使用故事板(现在使用SnapKit)在同一屏幕上执行的代码。

为此,需要删除故事板文件并从AppDelegate实例化View Controller。 在测试中,我们使用let viewController = ViewController()实例化类ViewController

打开FailureDiffs文件夹,看看发生了什么:

似乎我们为图像视图约束设置了错误的值。 如我们所见,它是UI中唯一看起来不同的对象。

我们看一下代码并进行更改:

  make.top.equalTo(titleLabel.snp.bottom).offset(100) 

具有以下内容:

  make.top.equalTo(titleLabel.snp.bottom).offset(60) 

使用Cmd + U再次测试,单元测试应该可以。

您可以在示例git仓库中的snapkit_refactor分支中使用SnapKit下载最终重构来进行checkout

在上一个示例中,我们看到了如何在iPhone 8上使用该工具。但是,如果我们想在支持的所有设备上进行测试,会发生什么?

我们必须随时配置测试以在不同的模拟器中进行测试。 不太实用的东西。

因此,我做了一个脚本,可以在我们想要的任何设备中进行测试。 与Fastlane Snapshot配置类似,但不支持语言。

要使用该脚本,需要在测试类的setUp()方法中放置self.isDeviceAgnostic = true以便将设备标识符(在这种情况下为设备分辨率)添加到文件名中。

该脚本是公共的,您可以在下面看到它。 您只需要更改workspacescheme值:

用于对不同设备进行快照的脚本

它生成所有指定设备的所有屏幕截图的输出。 要查看任何屏幕截图中是否存在错误,我们必须查看其生成的输出。

在该示例中,我仅包括了iPhone设备,但它允许我们项目支持的任何其他设备。

稍后,我将尝试执行相同的脚本,但是增加了对语言的支持,生成了一个显示所有结果的网页。 与Fastlane快照的工作原理类似。

我们学习了如何使用简单但功能强大的工具,该工具使我们能够检查是否意外在UI中添加了任何不必要的更改。

我们看到了一个示例,该示例说明如何从情节提要中重构视图以使用SnapKit,以及FBSnapshotTestcase如何允许我们检查是否正确执行了操作。

我还共享了一个脚本,该脚本允许在任何指定的设备上创建屏幕截图。 我认为,当我们拥有在所有分辨率下需要特别注意的不同设计时,这很有用。

如果您喜欢这篇文章,或者看到可以解决的任何错误,请随时发表评论或发送电子邮件至fedejordan99@gmail.com

非常感谢你! 😀