单元测试中如何比较INStartCallContactResolutionResult对象
我正在对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 failed: ("<INStartCallContactResolutionResult:
0x600002109310> {
resolutionResultCode = Success;
resolvedValue = <INPerson: 0x600002c7b780> {
displayName = Steve Jobs;
contactIdentifier = ;
nameComponents = ;
image = ;
customIdentifier = ;
relationship = ;
siriMatches = ;
personHandle = <INPersonHandle: 0x600000d78960> {
value = ;
type = Unknown;
label = ;
};
};
disambiguationItems = ;
itemToConfirm = ;
unsupportedReason = 0; }") is not equal to ("<INStartCallContactResolutionResult: 0x6000021092c0> {
resolutionResultCode = Success;
resolvedValue = <INPerson: 0x600002c7b900> {
displayName = Steve Jobs;
contactIdentifier = ;
nameComponents = ;
image = ;
customIdentifier = ;
relationship = ;
siriMatches = ;
personHandle = <INPersonHandle: 0x600000d78d80> {
value = ;
type = Unknown;
label = ;
};
};
disambiguationItems = ;
itemToConfirm = ;
unsupportedReason = 0; }")
可见两个对象的所有属性完全一致,但断言仍然失败,推测是苹果官方未为该类型实现相等判定方法。这导致我无法完成该函数的测试,请问有没有其他方案可以实现该测试场景?
出现这个问题的原因是苹果的Intent相关类型没有实现原生的相等判断,默认XCTAssertEqual对NSObject子类是判断指针是否相同,两个不同实例就算属性完全一致也会判定为不相等,可通过以下两种方案实现测试:
方案1:拆解属性分别断言(最稳妥)
分别校验分辨率结果的状态码、关联的联系人属性即可,不需要直接对比整个结果对象,修改后的测试代码示例如下:
func testResolveContacts() { let expectedDisplayName = "Steve Jobs" let person = INPerson(personHandle: INPersonHandle(value: nil, type: .unknown), nameComponents: nil, displayName: expectedDisplayName, 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() } // 校验分辨率结果状态为成功 XCTAssertEqual(firstResult.resolutionResultCode, .success) // 校验解析到的联系人属性符合预期 guard let resolvedPerson = firstResult.resolvedValue else { return XCTFail("成功状态下应该返回解析后的联系人") } XCTAssertEqual(resolvedPerson.displayName, expectedDisplayName) XCTAssertEqual(resolvedPerson.personHandle?.type, .unknown) XCTAssertNil(resolvedPerson.personHandle?.value) // 其他你需要校验的属性都可以在这里加断言 } }
如果需要测试其他分支(比如多联系人、无匹配联系人的情况),同样可以校验对应的resolutionResultCode、unsupportedReason、disambiguationItems等属性即可。
方案2:封装相等判断辅助方法
如果多个测试用例都需要对比INStartCallContactResolutionResult,可以写一个全局的辅助判断函数,复用校验逻辑:
func resolutionResultEqual(_ lhs: INStartCallContactResolutionResult, _ rhs: INStartCallContactResolutionResult) -> Bool { guard lhs.resolutionResultCode == rhs.resolutionResultCode else { return false } switch lhs.resolutionResultCode { case .success: guard let lhsPerson = lhs.resolvedValue, let rhsPerson = rhs.resolvedValue else { return false } return lhsPerson.displayName == rhsPerson.displayName && lhsPerson.personHandle == rhsPerson.personHandle && lhsPerson.contactIdentifier == rhsPerson.contactIdentifier // 其他需要对比的属性 case .unsupported: return lhs.unsupportedReason == rhs.unsupportedReason case .disambiguation: guard let lhsItems = lhs.disambiguationItems, let rhsItems = rhs.disambiguationItems else { return false } // 对比待选列表的属性 return lhsItems.count == rhsItems.count default: return true } } // 测试时使用 XCTAssertTrue(resolutionResultEqual(firstResult, expectedResult))
内容的提问来源于stack exchange,提问作者johnny

