【问题标题】:How can I compare INStartCallContactResolutionResult objects for unit testing?如何比较 INStartCallContactResolutionResult 对象以进行单元测试?
【发布时间】:2021-12-07 14:25:34
【问题描述】:

我正在尝试对 INStartCallIntent 的 Intent Handler 类进行单元测试,但在比较结果对象以进行联系人解析时遇到问题。

例如,给定 INStartCallIntent 的基本处理程序:

import Intents

class StartCallHandler: NSObject, INStartCallIntentHandling {
        func resolveContacts(for intent: INStartCallIntent, with completion: @escaping ([INStartCallContactResolutionResult]) -> Void) {
        guard let contacts = intent.contacts, !contacts.isEmpty, let person = contacts.first else {
            completion([.needsValue()])
            return
        }
        
        guard contacts.count == 1 else {
            completion([.unsupported(forReason: .multipleContactsUnsupported)])
            return
        }

        let matchingContacts = [person] // matching logic here
        switch matchingContacts.count {
        case 2  ... Int.max:
            // We need Siri's help to ask user to pick one from the matches.
            completion([.disambiguation(with: matchingContacts)])
        case 1:
            // We have exactly one matching contact
            completion([.success(with: person)])
        default:
            completion([.unsupported(forReason: .noContactFound)])
        }
    }
}

如果我创建一个简单的单元测试,我无法比较 INStartCallContactResolutionResult 对象:

func testResolveContacts() {
    let person = INPerson(personHandle: INPersonHandle(value: nil, type: .unknown), nameComponents: nil, displayName: "Steve Jobs", image: nil, contactIdentifier: nil, customIdentifier: nil)
    let intent = INStartCallIntent(audioRoute: .unknown, destinationType: .unknown, contacts: [person], recordTypeForRedialing: .unknown, callCapability: .audioCall)
    let handler = StartCallHandler()
        
    handler.resolveContacts(for: intent) { result in
        XCTAssertEqual(result.count, 1)
        guard let firstResult = result.first else { return XCTFail() }
        
        let expectedPerson = INPerson(personHandle: INPersonHandle(value: nil, type: .unknown), nameComponents: nil, displayName: "Steve Jobs", image: nil, contactIdentifier: nil, customIdentifier: nil)
        let expectedResult = INStartCallContactResolutionResult(.success(with: expectedPerson))
        XCTAssertEqual(firstResult, expectedResult)
    }
}

XCTAssertEqual 失败并显示以下消息:

XCTAssertEqual 失败:(“ { 解决结果代码 = 成功; 已解决的值 = { 显示名称 = 史蒂夫乔布斯; 联系人标识符 = ; 名称组件 = ; 图像 = ; 自定义标识符 = ; 关系 = ; SiriMatches = ; personH​​andle = { 值 = ; 类型=未知; 标签 = ; }; }; 消歧义项 = ; itemToConfirm = ; 不支持的原因 = 0; }") 不等于 (" { 解决结果代码 = 成功; 已解决值 = { 显示名称 = 史蒂夫乔布斯; 联系人标识符 = ; 名称组件 = ; 图像 = ; 自定义标识符 = ; 关系 = ; SiriMatches = ; personH​​andle = { 值 = ; 类型=未知; 标签 = ; }; }; 消歧义项 = ; itemToConfirm = ; 不支持的原因 = 0; }")

因此,即使两个对象具有相同的属性,XCTAssertEqual 也可能会失败,可能是因为 Apple 端没有实现相等函数。

因此,几乎不可能测试此功能。有没有人能够以其他方式完成此任务?

【问题讨论】:

  • 有些东西没有加起来,在您提供的代码中expectedINPerson 的一个实例,但是错误消息显示比较了两个INStartCallContactResolutionResult。这里的某些东西似乎已经过时了,无论是代码还是失败消息。
  • 你是对的。我的示例代码是错误的。预期应该是 INStartCallContactResolutionResult 对象,而不是 INPerson。我会更新的。
  • 我认为您需要自己实现对 Equatable 的一致性。
  • 我试过了,但是有一个编译错误:Conformance of 'INStartCallContactResolutionResult' to protocol 'Equatable' was already stated in the type's module 'Intents'。此外,示例代码现在应该更新。

标签: ios swift xctest sirikit


【解决方案1】:

我在这里最终做的是将联系人解析器逻辑放在一个单独的帮助器类中,并将 INStartCallContactResolutionResult 类包装到一个自定义枚举中,该枚举基本上只是 1:1 映射它。

public enum PersonResolverUnsupportedReason {
    case startCallContactUnsupportedReason(INStartCallContactUnsupportedReason)
}

public enum PersonResolverResult {
    case success(INPerson)
    case disambiguation([INPerson])
    case needsValue
    case unsupported
    case unsupportedWithReason(PersonResolverUnsupportedReason)
    case skip
    
    var startCallContactResolutionResult: INStartCallContactResolutionResult {
        switch self {
        case let .success(person):
            return .success(with: person)
        case let .disambiguation(persons):
            return .disambiguation(with: persons)
        case .needsValue:
            return .needsValue()
        case .unsupported:
            return .unsupported()
        case let .unsupportedWithReason(reason):
            switch reason {
            case let .startCallContactUnsupportedReason(startCallReason):
                return .unsupported(forReason: startCallReason)
            }
        case .skip:
            return .notRequired()
        }
    }
}

public protocol PersonResolverProtocol: AnyObject {
    func attemptToResolvePerson(_ person: INPerson, with: @escaping ([PersonResolverResult]) -> Void)
}

public class PersonResolver: PersonResolverProtocol {
    public func attemptToResolvePerson(_ person: INPerson, with completion: @escaping ([PersonResolverResult]) -> Void) {
        let matchingContacts = [person] // matching logic here
        switch matchingContacts.count {
        case 2...Int.max:
            completion([.disambiguation(matchingContacts.map { INPerson(...) })])
        case 1:
            guard let matchingContact = matchingContacts.first else {
                completion([.unsupportedWithReason(.startCallContactUnsupportedReason(.noContactFound))])
                break
            }
            completion([.success(INPerson(...))])
        default:
            // no contacts match
            completion([.unsupportedWithReason(.startCallContactUnsupportedReason(.noContactFound))])
        }
    }
}

所以现在我可以:

  • 更好地对联系解析器逻辑进行单元测试
  • 将此解析器注入 StartCallHandler
  • 如果需要,在其他意图处理程序类中重新使用联系解析器类。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-01-13
    • 2011-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-10
    • 1970-01-01
    • 2012-06-03
    相关资源
    最近更新 更多