Swift:XCTest类‘FirstDemoTests’无初始化器,使用init()是否有问题?
首先,咱们先搞清楚你为什么会遇到“Class 'FirstDemoTests' has no initializers”这个错误——大概率是你的测试类里定义了非可选且未初始化的存储属性,Swift要求所有存储属性在实例化完成前必须有值,而XCTestCase的默认初始化逻辑没有帮你处理这些自定义属性,所以编译器报错了。
你通过添加init()解决了编译错误,但这个做法其实存在潜在风险,原因如下:
XCTest框架的初始化逻辑
XCTest是基于Objective-C运行时的框架,它实例化测试类时,通常会使用init(invocation: NSInvocation?)这个指定初始化器(而不是无参的init()),用来传递测试方法的调用信息。如果你自定义了无参的init(),但没有正确覆盖或衔接父类的初始化流程,会出现两种问题:
- 如果你在
init()里只调用了super.init(),实际上是调用了父类的默认初始化,但父类的指定初始化器是带invocation参数的,这会导致初始化流程不完整,测试运行时可能出现未知的崩溃或行为异常。 - 框架可能根本不会调用你的自定义
init(),而是直接使用init(invocation:),这时候你的属性初始化逻辑不会被执行,依然会触发运行时错误。
更安全的替代方案
与其自定义init(),不如针对存储属性的初始化问题,用更符合XCTest规范的方式解决:
1. 使用可选类型或隐式可选类型
把属性声明为可选(?)或隐式可选(!),然后在setUp()方法里完成初始化——这是XCTest中最常用的方式,因为setUp()会在每个测试方法执行前被调用,能保证属性在测试时处于可用状态:
class FirstDemoTests: XCTestCase { var myProperty: MyType? // 可选类型 override func setUp() { super.setUp() myProperty = MyType() // 在测试前初始化 } func testExample() { XCTAssertNotNil(myProperty) // 使用myProperty?.xxx } }
2. 使用延迟加载(lazy var)
如果属性的初始化不需要依赖外部参数,可以用lazy var延迟加载,这样既不用在初始化时赋值,也能避免隐式解包:
class FirstDemoTests: XCTestCase { lazy var myProperty: MyType = { return MyType() }() func testExample() { // 第一次访问时自动初始化 XCTAssertNotNil(myProperty) } }
3. 重写正确的父类初始化器
如果必须在初始化阶段就给属性赋值,应该重写XCTestCase的指定初始化器init(invocation:),同时实现init?(coder:)(因为XCTestCase遵循NSCoding协议):
class FirstDemoTests: XCTestCase { let myProperty: MyType // 非可选存储属性 override init(invocation: NSInvocation?) { // 先初始化自己的属性 self.myProperty = MyType() // 再调用父类的指定初始化器 super.init(invocation: invocation) } // 必须实现的NSCoding初始化器 required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func testExample() { XCTAssertNotNil(myProperty) } }
总结
在Swift 4/Xcode 9.2的环境下,直接在XCTest类中添加无参init()是不推荐的,很可能破坏XCTest框架的初始化流程,导致测试运行异常。优先选择上述的替代方案,既能解决编译器错误,又能保证测试的稳定性。
内容的提问来源于stack exchange,提问作者user2924482

