在隔离视图上进行 iOS UI 测试
Posted
技术标签:
【中文标题】在隔离视图上进行 iOS UI 测试【英文标题】:iOS UI Testing On an Isolated View 【发布时间】:2015-12-17 17:38:03 【问题描述】:我正在尝试将 UI 测试合并到我的 ios 项目中,但一直困扰着我的一件事是,您编写的所有测试似乎都必须从应用程序的开头开始并逐步完成.例如,如果我想测试登录屏幕后面的视图,我的测试必须首先在登录屏幕上运行,输入用户名/密码,单击登录,然后转到我要测试的视图。理想情况下,登录视图和下一个视图的测试将完全隔离。有没有办法做到这一点,还是我完全错过了 UI 测试背后的理念?
【问题讨论】:
我真的非常非常希望它能够很好地工作:应用程序启动干净,测试代码决定如何呈现 VC,测试代码使用自动化与 UI 交互。这是 UI 单元测试。我对这个主题的所有研究都在这里gist.github.com/fulldecent/529849bc5dd4464bbde2也许其他人可以拿起火炬。 【参考方案1】:绝对!
您需要的是一个干净的应用程序环境,您可以在其中运行您的测试 - 一张白纸。
所有应用程序都有一个应用程序委托,它设置应用程序的初始状态并在启动时提供一个根视图控制器。出于测试的目的,您不希望这种情况发生 - 您需要能够单独进行测试,而不会发生所有这些事情。理想情况下,您希望能够对屏幕进行欠测,并且只加载该屏幕,而不会发生其他状态更改。
为此,您可以创建一个仅用于实现UIApplicationDelegate
的测试对象。您可以通过启动参数告诉应用程序以“测试模式”运行并使用特定于测试的应用程序委托。
目标-C: main.m:
int main(int argc, char * argv[])
NSString * const kUITestingLaunchArgument = @"org.quellish.UITestingEnabled";
@autoreleasepool
if ([[NSUserDefaults standardUserDefaults] valueForKey:kUITestingLaunchArgument] != nil)
return UIApplicationMain(argc, argv, nil, NSStringFromClass([TestingApplicationDelegate class]));
else
return UIApplicationMain(argc, argv, nil, NSStringFromClass([ProductionApplicationDelegate class]));
斯威夫特: main.swift:
let kUITestingLaunchArgument = "org.quellish.UITestingEnabled"
if (NSUserDefaults.standardUserDefaults().valueForKey(kUITestingLaunchArgument) != nil)
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(UIApplication), NSStringFromClass(TestingApplicationDelegate))
else
UIApplicationMain(Process.argc, Process.unsafeArgv, NSStringFromClass(UIApplication), NSStringFromClass(AppDelegate))
您必须从您的 Swift 类中删除任何 @UIApplicationMain
注释。
对于“应用程序测试”,请务必在 Xcode 中设置方案的“测试”操作以提供启动参数:
对于 UI 测试,您可以将启动参数设置为测试的一部分:
目标-C:
XCUIApplication *app = [[XCUIApplication alloc] init];
[app setLaunchArguments:@[@"org.quellish.UITestingEnabled"] ];
[app launch];
斯威夫特:
let app = XCUIApplication()
app.launchArguments = [ "org.quellish.UITestingEnabled" ]
app.launch()
这允许测试使用专门用于测试的应用程序委托。这为您提供了很多控制权——您现在可以使用一张白纸进行测试。测试应用程序委托可以加载特定的故事板或放置一个空的UIViewController
。作为 UI 测试的一部分,您可以实例化被测视图控制器并将其设置为keyWindow
的根视图控制器或以模态方式呈现。一旦它被添加或呈现,您的测试就可以执行,完成后删除或关闭它。
【讨论】:
这个解决方案看起来很不错!但是,'NSUserDefaults.standardUserDefaults().valueForKey(kUITestingLaunchArgument) != nil' 对我不起作用。我将其更改为 NSProcessInfo.processInfo().arguments.contains(kUITestingLaunchArgument) 以使其工作。 @FyodorVolchyok 我强烈建议提交雷达,因为用户默认值应该可以工作。启动参数取代参数域中的其他默认值。 @quellish 您无法在测试中轻松加载 UIStoryboard(除非您在帖子 quellish.tumblr.com/post/135415677047/… 中公开私有变量)。我所做的是使用额外的参数,以便 TestAppDelegate 知道 UIStoryboard 和 UIViewController 实例化。【参考方案2】:如果您不介意加载原始 UI,只需使用以下命令跳转到目标 UI:
override func setUp()
super.setUp()
continueAfterFailure = false
XCUIApplication().launch()
let storyboard = UIStoryboard(name: "MainStoryboard", bundle: NSBundle.mainBundle())
let controller = storyboard.instantiateViewControllerWithIdentifier("LanguageSelectController")
UIApplication.sharedApplication().keyWindow?.rootViewController = controller
如果您不希望下面的原始 UI 加载,那么也可以从您的测试中传递:
app.launchArguments.append("skipEntryViewController")
然后在didFinishLaunchingWithOptions
,可以查看:
if NSProcessInfo.processInfo().arguments.contains("skipEntryViewController")
// then do NOT call makeKeyAndVisible
【讨论】:
问题指出需要的是干净和隔离的测试,这不是。 怎么不满足这个要求? 保持相同的应用程序委托和原始 UI 加载可能会做一些可能会干扰的工作。 在 [UIApplication sharedApplication].keyWindow.rootViewController 我得到 null 知道为什么吗? 即使名称正确也无法找到情节提要???我们需要为此导入任何东西吗??【参考方案3】:很遗憾,您描述的场景无法使用 UI 测试。
我采取的一种解决方法是将我的测试分组为功能“流”。例如,假设我要测试功能 A、功能 B 和功能 C。我需要登录才能使这三个功能都工作。
对于每个测试,我不启动应用程序,登录,然后最后运行实际测试。相反,我启动应用程序并登录一次。然后我将我的测试分为三个私有辅助方法,testFeatureA()
、testFeatureB()
和 testFeatureC()
。
通过创建单个流程,测试套件的运行时间将大大缩短。最大的缺点是,如果功能 A 失败,那么功能 B 将永远不会被测试。仅当您关心 所有 测试是否通过时,才应使用此方法。
使用 XCTest helper 和 __LINE__
和 __FILE__
参数默认的奖励积分。然后你可以将它们传递到你的XCTFail()
调用中,以在testFeatureA()
上显示失败行。
【讨论】:
投反对票,因为它实际上是可能的(见平淡的答案)以上是关于在隔离视图上进行 iOS UI 测试的主要内容,如果未能解决你的问题,请参考以下文章