NSOpenPanel 在 macOS 上中断 UI 测试
NSOpenPanel Breaks UI Testing on macOS
我正在使用 Xcode 在具有 com.apple.security.files.user-selected.read-write
授权的沙盒 macOS 应用程序上进行 UI 测试(即,可以访问用户通过一个 NSOpenPanel
GUI).
我注意到代码覆盖率在以模态方式呈现打开的面板后立即停止。这是我的代码:
@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
,选择打开它的文件夹。)
代码覆盖率最终突出显示如下:
我已经尝试用 fatalError()
调用替换 switch
语句,但 UI 测试仍然成功完成,提示紧接着:
let response = panel.runModal()
...在测试期间未执行。
禁用沙箱似乎没有效果,所以我怀疑是 运行 模态打开的面板导致了麻烦...
我尝试了所有其他可用的方法来呈现打开的面板,即:
panel.begin { (response) in
switch response {
case NSApplication.ModalResponse.OK:
self.openPanelDidSelectURL(panel.urls[0])
default:
return
}
}
...还有:
panel.beginSheetModal(for: view.window!) { (response) in
switch response {
case NSApplication.ModalResponse.OK:
self.openPanelDidSelectURL(panel.urls[0])
default:
return
}
}
...但结果始终相同: 测试期间不涵盖显示面板后立即执行的所有代码。
最后,我意识到我的 UI 测试不能依赖于打开面板所在的任何用户可选择的文件夹(上次访问的目录?) ,所以我选择使用 mocking。
首先,在我的UI测试中类,我采用了这个设置逻辑:
override func setUp() {
continueAfterFailure = false
let app = XCUIApplication()
app.launchArguments.append("-Testing")
app.launch()
}
("Testing" 之前的连字符是强制性的,否则我基于文档的 macOS 应用程序会认为我启动它是为了打开一个名为 "Testing" 的文档, 并且没有这样做)
接下来,在应用端,我定义了一个全局计算属性来确定我们是否运行在测试中:
public var isTesting: Bool {
return ProcessInfo().arguments.contains("-Testing")
}
最后,也是在应用程序方面 我将所有 NSOpenPanel
调用包装成两种方法:一种用于提示用户输入要读取的文件,另一种用于提示用于写入结果文件的输出目录的用户(这是我的应用程序在 NSOpenPanel
中的全部需求):
public func promptImportInput(completionHandler: @escaping (([URL]) -> Void)) {
guard isTesting == false else {
/*
Always returns the URLs of the bundled resource files:
- 01@2x.png,
- 02@2x.png,
- 03@2x.png,
...
- 09@2x.png,
*/
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
而不是模拟用户选择 files/directories 的部分仍然被排除 收集代码覆盖率统计信息(但这一次,这是设计使然)。
不过至少现在只有这两个地方了。我的其余代码只是调用这两个函数,不再直接与 NSOpenPanel
交互。 'abstracted' OS 的文件浏览界面远离我的应用程序...
我正在使用 Xcode 在具有 com.apple.security.files.user-selected.read-write
授权的沙盒 macOS 应用程序上进行 UI 测试(即,可以访问用户通过一个 NSOpenPanel
GUI).
我注意到代码覆盖率在以模态方式呈现打开的面板后立即停止。这是我的代码:
@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
,选择打开它的文件夹。)
代码覆盖率最终突出显示如下:
我已经尝试用 fatalError()
调用替换 switch
语句,但 UI 测试仍然成功完成,提示紧接着:
let response = panel.runModal()
...在测试期间未执行。
禁用沙箱似乎没有效果,所以我怀疑是 运行 模态打开的面板导致了麻烦...
我尝试了所有其他可用的方法来呈现打开的面板,即:
panel.begin { (response) in
switch response {
case NSApplication.ModalResponse.OK:
self.openPanelDidSelectURL(panel.urls[0])
default:
return
}
}
...还有:
panel.beginSheetModal(for: view.window!) { (response) in
switch response {
case NSApplication.ModalResponse.OK:
self.openPanelDidSelectURL(panel.urls[0])
default:
return
}
}
...但结果始终相同: 测试期间不涵盖显示面板后立即执行的所有代码。
最后,我意识到我的 UI 测试不能依赖于打开面板所在的任何用户可选择的文件夹(上次访问的目录?) ,所以我选择使用 mocking。
首先,在我的UI测试中类,我采用了这个设置逻辑:
override func setUp() {
continueAfterFailure = false
let app = XCUIApplication()
app.launchArguments.append("-Testing")
app.launch()
}
("Testing" 之前的连字符是强制性的,否则我基于文档的 macOS 应用程序会认为我启动它是为了打开一个名为 "Testing" 的文档, 并且没有这样做)
接下来,在应用端,我定义了一个全局计算属性来确定我们是否运行在测试中:
public var isTesting: Bool {
return ProcessInfo().arguments.contains("-Testing")
}
最后,也是在应用程序方面 我将所有 NSOpenPanel
调用包装成两种方法:一种用于提示用户输入要读取的文件,另一种用于提示用于写入结果文件的输出目录的用户(这是我的应用程序在 NSOpenPanel
中的全部需求):
public func promptImportInput(completionHandler: @escaping (([URL]) -> Void)) {
guard isTesting == false else {
/*
Always returns the URLs of the bundled resource files:
- 01@2x.png,
- 02@2x.png,
- 03@2x.png,
...
- 09@2x.png,
*/
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
而不是模拟用户选择 files/directories 的部分仍然被排除 收集代码覆盖率统计信息(但这一次,这是设计使然)。
不过至少现在只有这两个地方了。我的其余代码只是调用这两个函数,不再直接与 NSOpenPanel
交互。 'abstracted' OS 的文件浏览界面远离我的应用程序...