如何在 Swift/XCTest 中写入本地文件?

Posted

技术标签:

【中文标题】如何在 Swift/XCTest 中写入本地文件?【英文标题】:How do I write to a local file in Swift/XCTest? 【发布时间】:2018-09-19 17:30:58 【问题描述】:

我的最终问题是关于使用 XCTest 和 Swift4(在与电视设备配对的 MacBook 上运行)保存 AppleTV 应用程序的屏幕截图,但我什至无法将简单的文本字符串写入本地文件。如果我能让这个简单的文件保存工作,我希望我能解决截图问题。 (抱歉让这看起来像两个问题,但它们似乎是相关的,并且是我的故障排除工作的结果。)

首先,根据我在网上某处找到的示例代码,这是我尝试使用屏幕截图执行的操作:

let appshot = XCUIApplication().windows.firstMatch.screenshot()
let shotpath = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0].appendingPathComponent("appshot.png")
let shotpathUrl = URL(string: "file://\(shotpath)")
print("Saving to: \(shotpath)")

do 
    try appshot.pngRepresentation.write(to: shotpathUrl!)
 catch 
    print("Failed saving screenshot due to \(error)")

这给了我以下输出:

Saving to: file:///var/mobile/Containers/Data/Application/77D52C66-353B-4029-97D5-48E6BAE35C92/Downloads/appshot.png
Failed saving screenshot due to Error Domain=NSCocoaErrorDomain Code=4 "The file “appshot.png” doesn’t exist." UserInfo=NSFilePath=///var/mobile/Containers/Data/Application/77D52C66-353B-4029-97D5-48E6BAE35C92/Downloads/appshot.png, NSUnderlyingError=0x1c405bc60 Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"

当然,该文件不存在,因为这是我正在尝试创建的文件。但是 /var/mobile 也不存在于我的笔记本电脑上——看起来 FileManager 正在构建的路径可能存在于 AppleTV 设备上,但我希望它在我的笔记本电脑上执行我的测试脚本。

所以我退出了一个更简单的案例,即使这样也给我带来了问题:

let str = "This is a test"
let path = "file:///Users/haljor/foo.txt"
let pathUrl = URL(string: path)!
print("Path: \(path)")
print("URL: \(pathUrl)")

do 
    try str.write(to: pathUrl, atomically: true, encoding: .utf8)
 catch 
    print("Caught error writing to \(pathUrl): \(error)")

这是输出:

Path: file:///Users/haljor/foo.txt
URL: file:///Users/haljor/foo.txt
Caught error writing to file:///Users/haljor/foo.txt: Error Domain=NSCocoaErrorDomain Code=4 "The folder “foo.txt” doesn’t exist." UserInfo=NSURL=file:///Users/haljor/foo.txt, NSUserStringVariant=Folder, NSUnderlyingError=0x1c40553f0 Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"

在这里,它似乎正在尝试写入我指定路径的文件夹,而不是文件。显然,在每种情况下,我都不理解某些东西。

我真的不喜欢使用完全指定的路径还是使用 FileManager 的东西——它只需要放在我的笔记本电脑(而不是电视设备)上的某个地方。我错过了什么?

【问题讨论】:

【参考方案1】:

您可以向测试用例添加附件并将其保存到磁盘。问题是容器中可能还不存在Downloads 文件夹。处理这个问题的最好方法是通过 init-once 属性:

var downloadsFolder: URL = 
    let fm = FileManager.default
    let folder = fm.urls(for: .downloadsDirectory, in: .userDomainMask)[0]

    var isDirectory: ObjCBool = false
    if !(fm.fileExists(atPath: folder.path, isDirectory: &isDirectory) && isDirectory.boolValue) 
        try! fm.createDirectory(at: folder, withIntermediateDirectories: false, attributes: nil)
    
    return folder
()

func test() 
    let appshot = XCUIScreen.main.screenshot()
    let attachment = XCTAttachment(screenshot: appshot)
    attachment.lifetime = .keepAlways
    self.add(attachment)

    // Save to container
    let url = downloadsFolder.appendingPathComponent("appshot.png")
    try! appshot.pngRepresentation.write(to: url)

如果要查看附件,请右键单击测试用例,选择跳转到报告并展开树。你最终会看到截图:

【讨论】:

截取屏幕截图的一个目的是比较不同时间的镜头之间的差异。测试用例是针对视频流的——暂停时,相隔几秒钟拍摄的两张照片应该是相同的。理想情况下,我会使用 ImageMagick 进行比较(如果 Swift 甚至允许我的话),但如果我什至无法将文件保存到我想要的位置,这听起来是不可能的。 我的立场是正确的。您可以将屏幕截图保存到磁盘将其附加到测试报告中。 保存到磁盘是我的问题 - 这是我无法获得的部分。 尝试这个更新的代码,我在 createDirectory 步骤中遇到了一个致命错误:Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “Downloads” in the folder “B9C0B1FD-14D2-4986-9775-B6EF76E4EC37”." UserInfo=NSFilePath=/var/mobile/Containers/Data/Application/B9C0B1FD-14D2-4986-9775-B6EF76E4EC37/Downloads, NSUnderlyingError=0x283bf6df0 Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted" 这是我之前遇到的同样的错误,试图在 /var/mobile 下创建一个文件夹 我猜你正在设备上进行测试。我在模拟器中对此进行了测试。 ios 不允许您创建 Downloads 文件夹,默认情况下没有创建任何文件夹。所以选择一个不同的文件夹,比如Document

以上是关于如何在 Swift/XCTest 中写入本地文件?的主要内容,如果未能解决你的问题,请参考以下文章

在 Swift XCTest 案例中,navigationController 为零

如何通过与 swift/xctest 协调来点击 iOS 主屏幕上的某个点

在 swift Xctest 中测试 Firebase 类

Swift:XCTest 会修改状态吗?可以返回可选值的测试函数的约定是啥?

使用 Realm 进行 Swift XCTest UI 测试

iOS 和 Swift xctest 上的 Google SDK 1.9.1 麻烦