Как отловить fatalError, вызванный во время UITest? - PullRequest
1 голос
/ 01 апреля 2020

UITest, о котором идет речь, запускает приложение, нажимает на ячейку, которая нажимает на экран для проверки, а затем завершается с ошибкой при помощи fatalError (), когда я делаю изменение, которое, как я ожидаю, вызовет fatalError ().

Как я могу поймать fatalError на UITest и использовать его, чтобы сообщить о сбое UITest?

Вот UITest:

class ConcreteFoodScreenUITests: XCTestCase
{
    let app = XCUIApplication()

    override func setUpWithError() throws {
        continueAfterFailure = false
        app.launch()
    }
    func testPathToConcreteFoodScreen() throws {
        //Tap Concrete Cell in FoodDashboard to go to the ConcreteFoodScreen
        XCTAssertTrue(app.otherElements["FoodDashboard"].exists)
        app.scrollViews.otherElements.tables.staticTexts["100 g, 100cal, P: 90g, F: 80g, C: 70g"].tap()

        //ConcreteFoodScreen
        XCTAssertTrue(app.otherElements["ConcreteFoodScreen"].exists)
        app.tables.cells.containing(.staticText, identifier:"Scale").children(matching: .textField).element.tap()
        app.keys["5"].tap()  //FIXME: Crashes with a Fatal Error

    }
}

Вот код, который запускается, что я хочу знать о:

class ScaleCellTextField: SWDecimalTextField {

    //there is more code here but not relevant

    func updateFoodEntry() {
        fatalError()
//        if let scale = Double(self.text!) {
//            concreteFood.scale = scale
//        }
    }
}

Вы можете видеть, что я закомментировал здесь некоторый код, чтобы он заработал.

1 Ответ

0 голосов
/ 01 апреля 2020

Боюсь, ты не сможешь. FatalError() завершает процесс вашего приложения, поэтому я не уверен, что вы можете отследить такого рода события.

РЕДАКТИРОВАТЬ :

Но ...

Вы можете использовать наши старые добрые уведомления Дарвина ... Чтобы добавить канал связи между вашими приложениями: тестируемым приложением и приложением-тестером.

Вам необходимо добавить файл в оба Ваши цели:

typealias NotificationHandler = () -> Void

enum DarwinNotification : String {
    case fatalError
}

class DarwinNotificationCenter {
    let center: CFNotificationCenter
    let prefix: String
    var handlers = [String:NotificationHandler]()

    init(prefix: String = "com.stackoverflow.answer.") {
        center = CFNotificationCenterGetDarwinNotifyCenter()
        self.prefix = prefix
    }

    var unsafeSelf: UnsafeMutableRawPointer {
        return Unmanaged.passUnretained(self).toOpaque()
    }

    deinit {
        CFNotificationCenterRemoveObserver(center, unsafeSelf, nil, nil)
    }

    func notificationName(for identifier: String) -> CFNotificationName {
        let name = prefix + identifier
        return CFNotificationName(name as CFString)
    }

    func identifierFrom(name: String) -> String {
        if let index = name.range(of: prefix)?.upperBound {
            return String(name[index...])
        }
        else {
            return name
        }
    }

    func handleNotification(name: String) {
        let identifier = identifierFrom(name: name)
        if let handler = handlers[identifier] {
            handler()
        }
    }

    func postNotification(for identifier: String) {
        let name = notificationName(for: identifier)
        CFNotificationCenterPostNotification(center, name, nil, nil, true)
    }

    func registerHandler(for identifier: String, handler: @escaping NotificationHandler) {
        handlers[identifier] = handler
        let name = notificationName(for: identifier)
        CFNotificationCenterAddObserver(center,
                                        unsafeSelf,
                                        { (_, observer, name, _, _) in
                                            if let observer = observer, let name = name {
                                                let mySelf = Unmanaged<DarwinNotificationCenter>.fromOpaque(observer).takeUnretainedValue()
                                                mySelf.handleNotification(name: name.rawValue as String)
                                            }
        },
                                        name.rawValue,
                                        nil,
                                        .deliverImmediately)
    }

    func unregisterHandler(for identifier: String) {
        handlers[identifier] = nil
        CFNotificationCenterRemoveObserver(center, unsafeSelf, notificationName(for: identifier), nil)
    }
}

extension DarwinNotificationCenter {
    func postNotification(for identifier: DarwinNotification) {
        postNotification(for: identifier.rawValue)
    }

    func registerHandler(for identifier: DarwinNotification, handler: @escaping NotificationHandler) {
        registerHandler(for: identifier.rawValue, handler: handler)
    }

    func unregisterHandler(for identifier: DarwinNotification) {
        unregisterHandler(for: identifier.rawValue)
    }
}

Затем просто в тестируемом приложении:

@IBAction func onTap(_ sender: Any) {
    // ... Do what you need to do, and instead of calling fatalError() 
    DarwinNotificationCenter().postNotification(for: .fatalError)
}

Чтобы поймать его в своем тесте, просто сделайте следующее:

// This is the variable you'll update when notified
var fatalErrorOccurred = false

// We need a dispatch group to wait for the notification to be caught
let waitingFatalGroup = DispatchGroup()
waitingFatalGroup.enter()

let darwinCenter = DarwinNotificationCenter()

// Register the notification
darwinCenter.registerHandler(for: .fatalError) {
    // Update the local variable
    fatalErrorOccurred = true
    // Let the dispatch group you're done
    waitingFatalGroup.leave()
}

// Don't forget to unregister 
defer {
    darwinCenter.unregisterHandler(for: .fatalError)
}

// Perform you tests, here just a tap
app.buttons["BUTTON"].tap()

// Wait for the group the be left or to time out in 3 seconds
let _ = waitingFatalGroup.wait(timeout: .now() + 3)

// Check on the variable to know whether the notification has been received
XCTAssert(fatalErrorOccurred)

И это все ...

Отказ от ответственности : Вы не должны использовать тестовый код в своем производственном коде, поэтому использование DarwinNotification не должно появляться в производственном процессе. Обычно я использую директиву компиляции, чтобы она была в моем коде только в отладке или тестах, а не в режиме выпуска.

...