Swift 单元测试突破口:如何优雅测试私有方法?
这里每天分享一个 iOS 的新知识,快来关注我吧
前言
在编写单元测试时,我们通常希望测试代码的业务逻辑。常用的测试编写格式是 GWT(Given-When-Then),其中在 "When" 步骤中调用我们想要测试的代码。然而,如果你曾写过测试用例,你可能遇到过无法测试私有方法的问题。
这是因为私有属性或方法的作用域仅限于其所在的类,因此我们无法在测试类中访问这些属性或方法。
例如,如果我们有一个类,其中包含一个私有方法,如下所示:
class SomeClass {
private func somePrivateMethod() {
// 一些逻辑代码
}
}
尝试在单元测试中调用 somePrivateMethod
,会发现无法访问,并会产生编译错误:“'somePrivateMethod' 由于 'private' 保护级别而无法访问”,这很容易理解。
class SomeClassTests: XCTestCase {
func testSomePrivateMethod() {
let testTarget = SomeClass()
testTarget.somePrivateMethod() // 错误:无法访问私有方法
}
}
那么我们该如何解决这个问题呢?如果 somePrivateMethod
包含一些业务逻辑,单元测试一定要能够覆盖,那么我们就必须找到其他可行的方法。
方法一:改变访问级别
一种方法是将这些方法的访问级别改为非私有,但这会将这些方法暴露给其他代码,显然这不是一个理想的方案。
public class SomeClass {
public func somePrivateMethod() {
// 一些逻辑代码
}
}
方法二:使用 TestHooks
TestHooks 可以派上用场了。我们可以创建测试钩子并利用它们来访问私有方法和属性,以进行单元测试。TestHooks 只是我们的类持有的一个钩子集,通过这些钩子可以提供对私有方法和属性的访问。
创建 TestHooks 时需要注意的几点:
-
TestHooks 是在我们希望访问其私有方法或属性的类的扩展中创建的(在我们的例子中是
SomeClass
),因为只有这样钩子才能访问那些属性或方法。 -
我们想访问的每个属性或方法都需要一个钩子。
-
建议将钩子扩展放在 DEBUG 宏中,以避免误用。
实现 TestHook:
以下是 SomeClass
的 TestHook 实现示例:
#if DEBUG // 在 debug 宏下添加以避免误用,并避免在发布环境中暴露私有方法
extension SomeClass { // 在类的扩展中编写,我们希望访问其私有方法
var testHooks: TestHooks { // testHooks 的实例,通过它我们将在单元测试中访问私有方法
TestHooks(target: self) // 使用 self 初始化以访问 self 的私有方法
}
struct TestHooks { // TestHooks 结构体,其中包含我们希望访问的所有属性和方法的钩子
var target: SomeClass // 需要访问其私有方法的目标
func somePrivateMethod() { // 暴露方法的钩子
target.somePrivateMethod() // 暴露该方法
}
}
}
#endif
这样一来,我们可以在单元测试文件中通过 testHooks
访问 somePrivateMethod
:
class SomeClassTests: XCTestCase {
func testSomePrivateMethod() {
let testTarget = SomeClass()
testTarget.testHooks.somePrivateMethod() // 通过 testHooks 访问私有方法
}
}
结尾
通过这种方式,我们可以在不改变代码结构的情况下,合理地测试私有方法。
TestHooks 提供了一种在测试环境中访问私有方法的途径,同时在发布环境中保持代码的封装性。这是一种在不破坏类封装原则的情况下进行单元测试的有效方法。
希望这能帮助到大家更好地进行 Swift 的单元测试,你对这种方式有什么看法呢?是否还有更好的方案分享,欢迎在评论区留言讨论。
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!