• Swift 单元测试突破口:如何优雅测试私有方法?
  • 发布于 1个月前
  • 94 热度
    0 评论
  • DuXing
  • 7 粉丝 48 篇博客
  •   
前言
在编写单元测试时,我们通常希望测试代码的业务逻辑。常用的测试编写格式是 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 时需要注意的几点:
1.TestHooks 是在我们希望访问其私有方法或属性的类的扩展中创建的(在我们的例子中是 SomeClass),因为只有这样钩子才能访问那些属性或方法。
2.我们想访问的每个属性或方法都需要一个钩子。

3.建议将钩子扩展放在 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 的单元测试,你对这种方式有什么看法呢?是否还有更好的方案分享,欢迎在评论区留言讨论。
用户评论