如何在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运行它时,模拟器将在每次运行时重新启动。 以前的版本没有这个问题,我想它将很快得到解决。