0

我正在为我的应用程序中的一些视图/视图控制器编写单元测试。

我的应用程序使用 UICollectionView,其中包含使用kingfisher加载的图像的单元格。我正在使用FBSnapshotTestCase记录视图的图像并将它们与已知的良好图像进行比较(顺便说一句,当我们的开发人员拥有拉取请求时,使用buddybuild 的CI 自动运行测试,这真的很酷)。

我正在使用NSURLSession-Mock将预先确定的数据(JSON 和图像)插入到测试中。

我的问题是编写测试似乎很难得到用户看到的最终结果。我经常发现(除非图像已经被缓存 - 它们不是因为我在测试设置中清除缓存以确保测试从干净状态运行!)我拍摄的所有屏幕截图都缺少图像,仅显示占位符。

4

1 回答 1

2

我已经找到了让这显然可靠地工作的方法,但我看不出我对我的解决方案 100% 满意。

首先,我在 didFinishLaunchingWithOptions 中执行此操作,以避免加载应用程序的主 UI,这在尝试为应用程序的主屏幕编写测试时会引起各种混乱:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    BuddyBuildSDK.setup()

    //Apply Itison UI Styles
    ItIsOnUIAppearance.apply()

    #if DEBUG
    if let _ = NSClassFromString("XCTest") {
        // If we're running tests, don't launch the main storyboard as
        // it's confusing if that is running fetching content whilst the
        // tests are also doing so.
        let viewController = UIViewController()
        let label = UILabel()
        label.text = "Running tests..."
        label.frame = viewController.view.frame
        label.textAlignment = .center
        label.textColor = .white
        viewController.view.addSubview(label)
        self.window!.rootViewController = viewController
        return true
    }
    #endif

然后在测试中,一旦我完全设置了 UIViewController 我需要做这样的事情:

    func wait(for duration: TimeInterval) {
        let waitExpectation = expectation(description: "Waiting")

        let when = DispatchTime.now() + duration
        DispatchQueue.main.asyncAfter(deadline: when) {
            waitExpectation.fulfill()
        }

        waitForExpectations(timeout: duration+1)
    }

    _ = viewController.view // force view to load
    viewController.viewWillAppear(true)
    viewController.view.layoutIfNeeded() // forces view to layout; necessary to get kingfisher to fetch images

    // This is necessary as otherwise the blocks that Kingfisher
    // dispatches onto the main thread don't run
    RunLoop.main.run(until: Date(timeIntervalSinceNow:0.1));
    viewController.view.layoutIfNeeded() // forces view to layout; necessary to get kingfisher to fetch images

    wait(for: 0.1)
    FBSnapshotVerifyView(viewController.view)

如果我不这样做,基本问题是 KingFisher 仅在 FBSnapshotVerifyView 强制布局视图时才开始加载图像,并且(因为 KingFisher 通过将块分派到后台线程来加载图像,然后将块分派回主线程)这为时已晚——发送到主线程的块无法运行,因为主线程在 FBSnapshotVerifyView() 中被阻塞。如果没有调用 'layoutIfNeeded()' 和 RunLoop.main.run(),到主队列的 KingFisher dispatch_async GCD 不会运行,直到 /next/ 测试让 runloop 运行,这为时已晚。

我对我的解决方案不太满意(例如,尚不清楚为什么我需要 layoutIfNeeded() 两次并运行 runloop 两次)所以真的很感激其他想法,但我希望这至少可以帮助其他遇到的人同样的情况,它需要一点点挠头才能弄清楚发生了什么。

于 2017-08-30T05:55:20.120 回答