如何在iOS应用中利用验收测试

与后端开发等其他领域相比,移动开发是一门新兴的学科。 由于苹果很长一段时间都不关心测试,因此社区开始进行测试的时间很晚。 最初,我们有单元测试工具,我研究过Swift中的单元测试和Objective-C中的单元测试。 进行单元测试很棒,但是顾名思义,它只是测试单元。 因此,我们继续寻找并找到了黄金大师测试。 另一个提高我们质量的好工具。 最后,我们可以确保我们的用户界面是正确的。 但是拥有正确的UI是否意味着我们的应用正在执行应做的事情? 那么,我们应该如何测试整个应用程序呢? 我们已经进行了UI测试。 太好了,我们可以确保我们的应用正确无误。 为我们的应用程序编写了一系列测试,大概两年了(包括2个iOS和Xcode更新),我们意识到:

UI测试缓慢,不稳定且难以维护

这么结束了吗? 我们必须忍受这些缓慢的测试,还是可以以某种方式改进它们? 让我们看一下UI测试为什么会这样。

UI测试

速度

Apple在UI测试中的解决方案是运行两个单独的进程。

每当测试请求XCUIElement时,TestProcess都会从原始应用程序获取整个应用程序状态。 然后它将执行整个状态的搜索查询,并列出所有匹配的元素(通常只有一个,但是在找到第一个元素之后仍会继续)。 知道了所有这些步骤之后,我们就可以知道在此架构内的时间:

  • 将AppState创建为XCTest的树
  • 将AppState传输到TestProcess
  • 查询整个AppState

使用Xcode 9,情况发生了一些变化。 它不是在TestProcess中执行查询,而是传输查询,而应用正在执行它。 此外,您可以使用“ .first”在树中找到第一个元素,然后立即返回。

这使UI测试更快,但是并没有我们希望的那样快。 而不是运行60分钟,他们可能需要52分钟。缓慢测试的另一个原因是动画。 您无法检查它们是否为真,因此可以选择停用它们:

UIView.setAnimationsEnabled(_ enabled: Bool) 

我不喜欢这种方法,因为您需要在生产代码中实现它,并且可能只有使用动画时才会出现错误。 最后,您将不得不考虑收益并决定是否要冒险。

稳定性

研究了速度问题后,为什么UI测试会出现问题?

由于测试是在自己的流程中运行的,因此我们知道它们必须同步。 但是为此,过程本身需要相互查找。 只要不是这种情况,测试就不会在您的应用程序内无故失败。 这不仅可以在测试的第一次开始时发生,而且可以在中间发生。 每当您的应用重启时,都需要找到该应用进程。 此外,有时由于意外的应用程序行为,导致测试运行不同步。 这可能只是系统对话框弹出而您不处理它,或者网络连接中出现小错误,但最终结果可能是毁灭性的。 知道我们的测试不稳定,就意味着每当运行失败时,就需要进行无休止的调试。 这可能是测试,但通常只是某种时机,(在测试内)意外行为,或者只是Xcode起作用。 但这仍然很耗时。

可维护性

在“屏幕对象”中,我们深入探讨了如何维护测试。 即使我们尝试坚持使用它们,我仍然经常看到与应用程序本身无关的代码。 .tap()之类的函数调用可防止测试松动。 用户界面的微小变化可能会杀死该屏幕上的所有测试。 提醒您,它通常很容易修复。 仍然需要很多时间。

我知道我在这里批评开发人员,但众所周知,没有人会失败。 最后,我们经常会碰到紧密结合的测试。

验收测试

既然我们已经研究了很多反对UI测试的原因,那么我们应该如何改进它们呢? 为此,我们需要问自己,我们真正想要实现的目标。 我认为要回答的最重要的问题是:

我们在编写正确的代码吗?

单元测试将回答以下问题:我们的代码是否孤立运行,但是是什么阻止我们编写导致以下结果的代码:

如上所述,我们可以插入某种输入并接收结果。 为此,我们必须编写一个相应的UI(称为夹具),该UI在我们的测试过程中会替换掉该UI。

测试决策表

让我们快速设计一下Connect Four的测试。 我们将使用一个决策表,其中包含一列带有我们的输入,一列带有我们的输出:

  |转|结果| 
| 0,1,0,2,0,3,0 |“玩家1获胜” |
| 0 |“播放器2:” |
| |“播放器1”:
| 0,1 |“播放器1:” |
| 0,1,2,1,2,1,2,1 |“玩家2获胜” |

转弯代表我们的输入,以逗号分隔,结果是预期的应用状态。 我们将跳过全板抽奖的可能性或其他选项,但您可以根据需要添加它们!

维基

在我们在应用程序中编写第一个固定装置之前,让我们看一下Fitnesse Wiki。

我们可以通过添加新的TestPage轻松添加新的测试清单。 在此页面中,列出了我们的测试。 幸运的是,我们测试的格式与上面使用的决策表相同。 这样我们就可以复制并粘贴它了:top第一行表示测试需要调用的夹具。

单击运行,将执行测试并在几秒钟而不是几分钟内返回结果。

Fitnesse集成到现有项目中

Slim是用于将Fitnesse集成到您的应用程序中的RPC服务器。 Objective-C端口为OCSlim。 我们可以手动完成(非常复杂的方式),也可以简单地使用CocoaPods(还有更多步骤)。 使用CocoaPods,我们必须执行以下操作:

  • 安装Xcode模板
  • 添加验收测试目标
  • 执行CocoaPods

安装Xcode模板非常简单。 从GitHub下载下载OCSlimProjectXcodeTemplates并运行make 。 您应该在Xcode中看到两种新型的测试模板:

  • 验收测试
  • 验收单元测试包

区别在于Wiki正在运行验收测试,验收单元测试包将在Xcode中报告结果。

下一步是添加“ AcceptanceTests”目标。 确保称呼它。 它应该使用其他名称工作,但否则我根本无法运行。

现在将以下内容添加到您的Podfile中:

 target 'AcceptanceTests' do 
platform :ios, 9.0
pod 'OCSlimProject'
end

当您打开工作区时,它可能会抱怨使用了错误的Swift版本,但这可以由自动转换器修复。

要运行测试,需要先运行验收测试目标,然后才能运行Wiki测试。 此外,还需要安装node和ios-sim:

  brew安装nodejs 
brew install ios-sim

书写装置

在Fitnesse中运行测试时,出现错误:

找不到类ConnectFourAT。

通过将类添加到我们的验收测试目标中,可以轻松解决此问题。

 @objc(ConnectFourAT) 
class ConnectFourAT: NSObject {
}

万一我们还没有决策表,我们的测试是绿色的,并且Wiki和我们的应用程序之间可以正常工作。

接下来,让我们添加上面的决策表,然后再次运行测试。 我们得到失败的测试:

找不到方法setTurns

为了解决这个问题,我们添加了turns变量和一个返回结果的函数

  @objc(ConnectFourAT) 
类ConnectFourAT:NSObject {
var匝数:[Int]

func result()->字符串{
返回“播放器1:”
}
}

Wiki测试中的结果是我们的预期结果。 为了使tobFitnesse变得显而易见,我们必须使用“?”。 因此,将其重命名为“结果?”。

现在,至少有一项测试将通过。

从此开始,您可以继续在夹具上添加接口并评估设置后的值。

其他工具

当然,除了Fitnesse,还有其他工具。 其中一些是仪表和机器人框架。 看看您喜欢哪一个。 既然有很多,请确保它们不使用UI进行测试。

结论

我们研究了UI测试不是完美解决方案的不同原因以及如何改善这种情况。 验收测试为我们遇到的大多数问题提供了一个很好的解决方案,但不能完全取代它们。 对于可能想知道为什么要引入这种设置的那些人,如果您只能在iOS上使用它,您可能会很高兴听到您也可以在Android上使用它。

此外,在同一实例上,您还可以为Java后端运行Fitnesse测试。

不过有一个问题。 当使用Xcode 9运行它时,模拟器将在每次运行时重新启动。 以前的版本没有这个问题,我想它将很快得到解决。