Swift 面向协议的编程和测试

2021-07-31 00:31:23

2015 年,Apple 引入了一个新概念——面向协议的编程。我鼓励您观看整个 50 分钟的 WWDC 视频。会议的一位演讲者阐述了所有令人敬畏的 Swift 新功能以及协议如何帮助编写更好、更健壮的代码。我敢肯定,您正在使用这些技巧中的大部分,甚至全部。是不是很棒?当然是,但 Swift 引入了一些限制。例如,你不能 swizzle Swift 方法。你可以用@objc 方法来做到这一点。测试框架,如 OCMock,使用此功能动态更改方法实现(当然,简单来说)。但是 Swift 类和它们的方法呢?嗯...您可以“生成”模拟作为 Xcode 中的构建步骤,我认为这不是一个完美的解决方案。行。您正在使用 UserDefaults 编写一些功能。也许是一个简单的保存用户关于黑暗主题偏好的决定。 class PreferenceSaver { var useDarkTheme: Bool { get { UserDefaults.standard.bool(forKey: "useDarkTheme") } set { UserDefaults.standard.set(newValue, forKey: "useDarkTheme") } }} 到目前为止,这将在生产,但是...如果需求发生变化怎么办?也许标准用户默认设置不够好?对于一个属性,更改没有问题,但是如果有 20 个首选项... 伙计,这是一个巨大的拉取请求。我将重构它以使其更有用:) class PreferenceSaver { private let defaults: UserDefaults init(defaults: UserDefaults = .standard) { self.defaults = defaults } var useDarkTheme: Bool { get { defaults.bool(forKey: " useDarkTheme") } set { defaults.set(newValue, forKey: "useDarkTheme") } }} 没有太大变化,但一切都改变了。现在 PreferenceSaver 有一个新的依赖项,可以在 init 中注入。此外,我添加了一个默认值 - 因此代码库中没有任何更改以适应新的初始化。

我们已经完成了一半。现在进行单元测试。要注入 UserDefaults 模拟,您必须继承并推动整个 Apple 类!这是压倒性的和麻烦的。 Defaults 类很简单,那么定位服务、Store Kit 类呢?它们庞大而复杂。 protocol UserDefaultsProtocol { func bool(forKey: String) -> Bool func set(_ value: Bool, forKey: String)}extension UserDefaults: UserDefaultsProtocol { }class PreferenceSaver { private let defaults: UserDefaultsProtocol init(defaults: UserDefaultsProtocol = UserDefaults.standard) { self.defaults = defaults } var useDarkTheme: Bool { get { defaults.bool(forKey: "useDarkTheme") } set { defaults.set(newValue, forKey: "useDarkTheme") } }} 再一次,我的改变没有完全影响当前代码库,因此使用 PreferenceSaver 不会更改文件。通过添加协议和扩展 UserDefaults,我们拥有原始实现的所有功能,并增加了可测试性! class PreferenceSaverMock: UserDefaultsProtocol { var dictionary = [String: Any]() func bool(forKey: String) -> Bool { return dictionary[forKey] as! Bool } func set(_ value: Bool, forKey: String) { dictionary[forKey] = value }} 现在断言一切,如果键存在,如果值正确。我们完全摆脱了 Apple 及其实施的束缚。使用 UserDefaultsProtocol 时,没有任何需求变化是可怕的。核心数据?没问题,保存到 plist?简单易行,最后,代码可以 100% 覆盖,没有任何麻烦。快乐编码和测试!