在隔离视图上进行 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 测试的主要内容,如果未能解决你的问题,请参考以下文章

[译] 使用 Espresso 隔离测试视图

[译] 使用 Espresso 隔离测试视图

springboot隔离@Async异步任务的线程池

隔离存储中的数据持续多长时间

08 | 事务到底是隔离的还是不隔离的?

Django单元测试测试视图问题,怎么解决