斯图尔特的苹果派食谱

我们如何在Stuart为我们的本机iOS应用程序运行自动化的UI测试。

每个厨师都有自己的食谱。 苹果派可以用多种样式制作,每种都可以用自己的方式美味……但是,有一个我们最喜欢的特殊食谱。 让我向您展示质​​量保证团队为在我们的iOS应用上执行UI测试而构建的Stuart测试框架配方。

我将通过苹果烤锡和我们选择的面粉与您交谈。

苹果

我要谈的第一个要素是测试框架,即我们的“苹果”。 有几个选项,例如Appium,EarlGrey,Calabash,但我们选择了XCTest。

但为什么?

  1. 这是苹果公司支持的框架(我们知道他们正在努力使它年复一年地变得更好);
  2. 它将完全集成在应用程序代码中。 这为我们的iOS开发人员添加/修复测试打开了一扇门,并且是一种鼓励他们(当然,是质量检查小组!)为同一功能请求中的每个功能或错误修正添加测试的方式。
  3. 最后但并非最不重要的一点是,我们可以在UI测试和单元测试之间共享模拟,这使得编写新测试非常顺利。

烤锡

选择合适的苹果后,我们要使馅饼变大。 当我切成薄片时,我个人讨厌它,它会破裂,因此Stuart Apple蛋糕将不得不抵制所做的更改并使外观保持惊人。 在开始做任何事情之前,我们必须先喘口气,然后想想在我们的项目中遵循哪种架构。 我们希望在使代码可重用且易于维护的同时保持简单性。

为了满足这些需求,页面对象模式将用于对应用程序的屏幕进行建模。 我们还将使用机器人模式,以将与应用程序的所有交互封装在一个地方。

测试将使用屏幕与应用程序进行交互,屏幕将使用机器人执行操作(即查找元素,获取文本,点击,滑动…)。

此外,我们创建了一个不同的应用程序目标,以使应用程序与测试脱钩。

这使我们可以在应用程序目标(和其他一些文件)中创建模拟,以定义这些模拟的默认值。 例如,我们有一个模拟客户端(在具有应用程序目标的文件中定义),将具有firstName John和lastName Doe。 这些变量在两个目标都共享的文件中定义,并且仅包含静态数据,因此即使我们要在测试目标中执行断言(甚至使用它构建模拟),我们也可以引用该数据。

面粉

馅饼几乎已经准备好放入烤箱了,但是我们错过了最后一个(也许是最重要的)配料:面粉。 普通面粉可以胜任,但优质面粉可以发挥作用。

我说“面粉”是什么意思?

我们不想通过我们的测试打入网络! 这些测试的目的是检查我们的应用程序的UI在特定的交互后是否以特定的方式运行。 如果由于API返回有效值但我们期望有所不同而导致测试失败,该怎么办? 如果网络请求花费太长时间怎么办? 测试可能会失败。 我们不希望每次由于网络请求而导致测试失败或(或者更糟)习惯于失败的测试时都收到烦人的通知。

我们对可以使我们实现该目标的内容进行了一些研究。 我们发现有趣的选项是OHHTTPStubs,Embassy / Ambassador,MockServer。 每个人都有优点和缺点,但是……我们的最终决定不是所有这些!

主要原因是我们不想在项目中添加外部依赖关系和/或维护大量API响应。 因此,我们使用了不同的方法。

让我们一步一步走。 我们要确保我们的APIClient不会影响网络。 让我们在APIClient中添加几行代码来完成工作并初始化我们的URLSession:

但是……我们如何知道测试是否为UITest以及MockUrlSession什么 ? 请耐心等待我的朋友,细节来了!

为了让该应用知道我们是否正在运行测试,让我们使用Apple提供给我们的一个很棒的功能:launchArgument和launchEnvironment。 在每次测试执行之前,我们将启动一个参数,指示我们正在运行测试。 每个测试的安装程序中的此方法都可以做到:

isUITest()方法将如下所示:

但是,如果我们除了模拟网络之外还想要其他配置,该怎么办? 例如,嘲笑当前位置? 让我们将所有这些放在一起。

下一步将创建一个MockAppDelegate,仅当我们处于UItest会话中时才充当AppDelegate。 结果如下:

为了仅在运行测试时使用MockAppDelegate,让我们创建一个main.swift文件,该文件将在我们运行UI测试时使用此MockAppDelegate。 因此,我们需要在测试设置中启动一个额外的环境。 我们称它为isRunningTest 。 main.swift的最终结果是:

让我们回顾一下目前的情况:

  1. 在每次测试执行之前,我们启动几个参数/环境。
  2. 我们使用MockAppDelegate代替常规的AppDelegate,后者的行为类似于应用程序,但允许我们模拟网络。
  3. 我们将所有网络请求转发到名为MockUrlSession()的自定义URLSession。

终于到了发现测试真正魔力的时候了。 MockUrlSession到底是什么? 最后,我们希望使其尽可能简单,因此它是一个URLSession,它将覆盖dataTask。

这会将所有网络请求转发到我们的自定义MockDataTask,最后,我们的MockDataTask将是:

getResponseBody()将返回响应和主体的状态码。

一片馅饼

让我们将解决方案付诸实践。 让我们进行最简单的测试:登录我们的应用程序。 为简单起见,让我们假设登录是端点api.apple.pie / login的POST操作。

能行吗 当然不是! 我们缺少了一些东西。 我们如何做到这一点,以便登录响应应用程序的预期? 我们需要其他一些东西来使其工作。

让我们使用我们可爱的环境来解决它。 我们将启动另一个在setUp方法中执行的环境:

bot.app.launchEnvironment["Login"] = "Success"

现在,我们必须配置成功的情况下我们的响应。 这将在下面提到的几行方法getResponseBody中完成:

如果我们想前进一步,避免维护API响应(前面提到的要求之一),则可以使用我们应用的DTO来构建响应。

作为这种模拟方法的一个缺点,因为将配置设置为此环境,所以在相同的测试执行期间,我们将无法对给定请求做出多个响应。 启动应用程序后,无法对其进行修改(在Apple文档launchEnvironmentlaunchArgument中指定了该属性 )。 目前,我们还没有遇到这种不便(这是一个好兆头,因为测试只能测试一种行为),但是我们在心中有一些解决方法来应对这种情况。

希望您喜欢这个食谱! 寄出带有结果的图片,以备您在家中使用时,不要犹豫,让我们知道一些特殊的肉桂,可以使苹果派变得更好!

喜欢你看到的吗? 我们正在招聘! 🚀 检查我们开放的工程职位