#swift #unit-testing
Вопрос:
У меня есть код, который выглядит следующим образом
class Vibration: NSObject {
var status: VibrationStatus // an enum
}
и функция в другом классе (типа NSObject), подобная следующей, которая является частью объекта, обладающего свойством вибрация типа Вибрация
func vibrate() {
DispatchQueue.main.async { [weak self] in
vibration.status = .vibrating
// do some real HW vibrate stuff
}
}
Ни одно из определений свойств или классов не включает @objc
или @objcMembers
Я пытаюсь создать тест, который будет ждать этого асинхронного вызова, чтобы установить vibration.status.
У меня есть тестовая функция, которая, кажется, работает (см. Ниже), когда я объявляю status
свойство как @objc
или помещаю @objcMembers
в Vibration
класс.
func testVibrate() {
let invite: SignalingInviteBody = SignalingInviteBody()
let incomingCall = IncomingCall(invite)
let expectation = XCTNSPredicateExpectation(predicate: NSPredicate(format: "status = 2"), object: incomingCall.vibration)
incomingCall.startRing() // this calls the function vibrate()
wait(for: [expectation], timeout: 3.0)
}
Этот тест также требует @objc
объявления перечисления с декларацией перечисления, совместимой с Objective-C, чего я не хочу, так как это было бы только для тестирования.
За исключением тестирования, нет необходимости создавать status
свойство @objc
или Vibration
класс как @objcMembers
, и я бы предпочел не менять код базовой программы на более неэффективный стиль, когда мне не нужна совместимость с Objective-C в базовой программе.
Есть ли способ проверить это по-настоящему, честно и быстро?
Ответ №1:
Предисловие: это просто какой-то псевдокод, который я быстро набрал в браузере. Вероятно, ему потребуется некоторая полировка, прежде чем он будет правильно скомпилирован.
Я бы использовал макет и инъекцию зависимостей:
class MockVibration: Vibration {
let statusChanged: (VibrationStatus) -> Void
init(statusChanged: (VibrationStatus) -> Void) {
self.statusChanged = statusChanged
}
var status: VibrationStatus {
didSet {
statusChanged(status)
}
}
}
У меня, вероятно, был бы протокол, и я бы Vibration
и то, и MockVibration
другое соответствовало ему, но наличие MockVibration: Vibration
должно хорошо работать. Как только вы определили этот макет, вы можете использовать его для выполнения ожиданий в своем тестовом примере:
func testVibrate() {
let didVibrate = self.expectation(description: "Started vibrating")
let mockVibration = MockVibration { newStatus in
XCTAssertEqual(newStatus, .vibrating)
didVibrate.fulfill()
}
let invite = SignalingInviteBody()
let incomingCall = IncomingCall(invite, mockVibration)
incomingCall.startRing() // this calls the function vibrate()
wait(for: [didVibrate], timeout: 3.0)
}
Возможно, вы даже сможете переработать этот интерфейс так, чтобы это DispatchQueue.main.async {}
происходило внутри Vibration
класса как внутренняя деталь. Если вы это сделаете, взаимодействие между IncomingCall
и Vibration
станет синхронным, поэтому вам не нужно будет использовать ожидания. Ваш тест на входящий вызов сократится до:
class MockVibration {
// The mock can just have its status set simply/synchronously
var status: VibrationStatus = .off // or some other "initial" value
}
func testVibrate() {
let mockVibration = MockVibration()
let invite = SignalingInviteBody()
let incomingCall = IncomingCall(invite, mockVibration)
incomingCall.startRing() // this calls the function vibrate()
XCTAssertEqual(mockVibration.status, .vibrating)
}
Конечно, тогда вам понадобится отдельный тест , который охватывает Vibration
и гарантирует, что его общедоступные API заставят его изменить свое внутреннее состояние, используя правильную очередь отправки или что-то еще.