NSOpenPanel打破了macOS上的UI测试

尼古拉斯·米亚里

我正在使用Xcode在具有com.apple.security.files.user-selected.read-write授权的沙盒macOS应用上进行UI测试(即,可以访问用户通过NSOpenPanelGUI明确选择的文件和文件夹)。

我注意到,打开面板以模态显示后,代码覆盖范围立即停止。这是我的代码:

@IBAction func go(_ sender: Any) {

    let panel = NSOpenPanel()
    panel.canCreateDirectories = true
    panel.canChooseDirectories = true
    panel.canChooseFiles = false
    panel.allowsMultipleSelection = false

    let response = panel.runModal()

    switch response {
    case NSApplication.ModalResponse.OK:
        openPanelDidSelectURL(panel.urls[0])

    default:
        return
    }
}

(我已经记录了我的UI测试,以便立即NSOpenPanel接受它,选择打开它的文件夹。)

代码覆盖率最终像这样突出显示:

在此处输入图片说明

我尝试用调用替换该switch语句fatalError(),但是UI测试仍然成功完成,并建议在紧随其后的所有内容:

let response = panel.runModal()

...is not executed during the test.

Disabling sandboxing seems to have no effect, so I suspect it is running the open panel modally that causes trouble...

Nicolas Miari

I tried all other available methods for presenting the open panel, namely:

panel.begin { (response) in
    switch response {
    case NSApplication.ModalResponse.OK:
        self.openPanelDidSelectURL(panel.urls[0])

    default:
        return
    }
}

...and also:

panel.beginSheetModal(for: view.window!) { (response) in
    switch response {
    case NSApplication.ModalResponse.OK:
        self.openPanelDidSelectURL(panel.urls[0])

    default:
        return
    }
}

...but the result is always the same: All code immediately after presenting the panel is not covered during tests.


In the end, I realized that my UI tests cannot rely on some user-selectable folder being present wherever the open panel lands (last visited directory?), so I opted for using mocking instead.

First, in my UI test classes, I adopted this setup logic:

override func setUp() {
    continueAfterFailure = false
    let app = XCUIApplication()
    app.launchArguments.append("-Testing")
    app.launch()
}

(the hyphen before "Testing" is mandatory, otherwise my document-based macOS app will think I am launching it to open a document named "Testing", and fail to do so)

Next, On the app side, I defined a global computed property to determine whether we are running under a test or not:

public var isTesting: Bool {
    return ProcessInfo().arguments.contains("-Testing")
}

Finally, also on the app side I wrapped all NSOpenPanel calls into two methods: One for prompting the user for input files to read, and another to prompt the user for an output directory into which to write the resulting files (this is all my app needs from NSOpenPanel):

public func promptImportInput(completionHandler: @escaping (([URL]) -> Void)) {
    guard isTesting == false else {
        /* 
          Always returns the URLs of the bundled resource files: 
           - [email protected], 
           - [email protected], 
           - [email protected],
             ...
           - [email protected], 
         */
        let urls = (1 ... 9).compactMap { (index) -> URL? in
            let fileName = String(format: "%02d", index) + "@2x"
            return Bundle.main.url(forResource: fileName, withExtension: "png")
        }
        return completionHandler(urls)   
    }
    // (The code below cannot be covered during automated testing)

    let panel = NSOpenPanel()
    panel.canChooseFiles = true
    panel.canChooseDirectories = true
    panel.canCreateDirectories = false
    panel.allowsMultipleSelection = true

    let response = panel.runModal()

    switch response {
    case NSApplication.ModalResponse.OK:
        completionHandler(panel.urls)
    default:
        completionHandler([])
    }
}

public func promptExportDestination(completionHandler: @escaping((URL?) -> Void)) {
    guard isTesting == false else {
        // Testing: write output to the temp directory 
        // (works even on sandboxed apps):
        let tempPath = NSTemporaryDirectory()
        return completionHandler(URL(fileURLWithPath: tempPath))
    }
    // (The code below cannot be covered during automated testing)

    let panel = NSOpenPanel()
    panel.canChooseFiles = false
    panel.canChooseDirectories = true
    panel.canCreateDirectories = true
    panel.allowsMultipleSelection = false

    let response = panel.runModal()

    switch response {
    case NSApplication.ModalResponse.OK:
        completionHandler(panel.urls.first)
    default:
        completionHandler(nil)
    }
}

这两个函数中使用实际函数NSOpenPanel而不是模拟用户选择的文件/目录的部分仍被排除在收集代码覆盖率统计信息之外(但是这是设计使然)。

但是至少现在只有这两个地方。我其余的代码仅调用这两个函数,不再NSOpenPanel直接交互我已经从应用程序“抽象”了操作系统的文件浏览界面...

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章