在 XCTest 测试中从情节提要加载的视图控制器中清空 UIView 快照(间歇性)

Posted

技术标签:

【中文标题】在 XCTest 测试中从情节提要加载的视图控制器中清空 UIView 快照(间歇性)【英文标题】:Empty UIView snapshots from view controller loaded from storyboard in XCTest tests (intermittent) 【发布时间】:2014-10-02 08:06:23 【问题描述】:

我们一直在跟踪我们的项目中的an issue,在该项目中,我们会间歇性地失败快照测试用例。我们方法的要点是渲染视图控制器的视图并将该图像与参考图像进行比较,以查看它们是否不同。我们的方法有几个层次:

FBSnapshotTestCases Quick Custom Matchers

我们的问题是有时渲染视图创建的图像是空的。只是一个大的(正确尺寸)透明图像。

我已经单独测试了每一个,并确定这些都不是问题。相反,我已经能够在一个独立的plain Xcode 项目中重现它。

通过使用 FBSnapshotTestCases 用来呈现视图的same approach,我创建了一个简单的测试。要重现,请创建“Master-Detail”模板的新项目,并为详细视图控制器提供“Detail”的 Storyboard ID。然后创建这个简单的单元测试。

func testExample1() 
    let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
    let sut = storyboard.instantiateViewControllerWithIdentifier("Detail") as UIViewController

    sut.beginAppearanceTransition(true, animated: false)
    sut.endAppearanceTransition()

    UIGraphicsBeginImageContextWithOptions(sut.view.frame.size, false, 0)
    sut.view.drawViewHierarchyInRect(sut.view.bounds, afterScreenUpdates: true)
    let image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()

    let data = UIImagePNGRepresentation(image)

    println("byte length: \(data.length)")

没什么太花哨的,它很可能会通过。但是,如果您将代码重复几次:

func testExample1()  ... 
func testExample2()  ... 
func testExample3()  ... 

输出很奇怪(截断):

Test Suite 'All tests' started at 2014-10-02 07:46:52 +0000
byte length: 27760
byte length: 17645
byte length: 27760
Test Suite 'All tests' passed at 2014-10-02 07:55:29 +0000.
     Executed 3 tests, with 0 failures (0 unexpected) in 517.778 (517.781) seconds

字节长度应该相同,但事实并非如此。第二个测试(有时是第三个)将有一个空视图,就像我们的问题一样。

here 提供了一个演示该问题的示例项目。

我能够使用 Objective-C 测试项目重现该问题,因此不太可能是 Swift 问题。在过去的项目中,我们没有将 Storyboards 用于我们的视图控制器 UI,因此可能需要一个额外的步骤来“强制”加载视图。这也可能是 Xcode 6.x 或 ios 8 的问题(我已经重现了 Xcode 6.0.1 的问题)。

有没有人遇到过这样的问题,从 Storyboard 加载的控制器的视图渲染图像是透明的?

【问题讨论】:

如果有人有兴趣,使用old way 渲染视图不会产生问题。我可能不得不在 Facebook 的图书馆上打开雷达和问题。 这几乎可以肯定是 UIKit 中的一个错误。与此同时,我正在使用 FBSnapshotTestCase 的一个分支(详情here)。我将归档一个雷达并发布一个指向 openradar 的链接。 对于任何在家关注的人,我已经打开了一个 radar 来描述这个问题。如果我收到回复,我会在那里更新。 【参考方案1】:

似乎可以解决问题...

    let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
    let sut = storyboard.instantiateViewControllerWithIdentifier("Detail") as UIViewController

    sut.beginAppearanceTransition(true, animated: false)
    sut.endAppearanceTransition()

    UIGraphicsBeginImageContextWithOptions(sut.view.frame.size, false, 0)
    let context = UIGraphicsGetCurrentContext
    sut.view.layer.renderInContext(context())
    let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    let data = UIImagePNGRepresentation(image)
    println("byte length: \(data.length)")

【讨论】:

是的。这实际上是 Facebook 库用来执行此操作的 old way。有一个open PR 提供使用它的选项。希望它尽快合并。【参考方案2】:

通过切换到生成的 UIView 将故事板排除在外:

let view = UIView(frame: CGRectMake(0, 0, 300, 300))
view.backgroundColor = UIColor.blueColor()

UIGraphicsBeginImageContextWithOptions(view.frame.size, false, 0)
view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()

let data = UIImagePNGRepresentation(image)

println("byte length: \(data.length)")

给出相似的结果。

Test Case '-[TestTestTests.TestTestTests testExample1]' started.
byte length: 9663
Test Case '-[TestTestTests.TestTestTests testExample1]' passed (1.000 seconds).
Test Case '-[TestTestTests.TestTestTests testExample2]' started.
byte length: 9663
Test Case '-[TestTestTests.TestTestTests testExample2]' passed (0.112 seconds).
Test Case '-[TestTestTests.TestTestTests testExample3]' started.
byte length: 6469

【讨论】:

【参考方案3】:

正如本主题所建议的,您尝试改用“[self.view.layer renderInContext:UIGraphicsGetCurrentContext()]”:

drawViewHierarchyInRect:afterScreenUpdates: delays other animations

【讨论】:

是的,这就是 Facebook 的图书馆过去所做的。在此期间,我已恢复使用旧行为。

以上是关于在 XCTest 测试中从情节提要加载的视图控制器中清空 UIView 快照(间歇性)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 swift 在情节提要中从一个视图控制器移动到另一个视图控制器 [重复]

如何在 swift 中从情节提要中的 didSelectRowAt indexPath 打开视图标签栏控制器?

以编程方式设置 rootViewController 不起作用,仅在 Xcode 11 中从情节提要中选择初始视图控制器

推送视图控制器后从情节提要加载视图

如何在情节提要中从一个视图转到另一个视图

无法从情节提要显式加载视图控制器