gpt4 book ai didi

swift - 在 Swift 中模拟单例/sharedInstance

转载 作者:搜寻专家 更新时间:2023-10-30 22:59:53 30 4
gpt4 key购买 nike

我有一个要使用 XCTest 测试的类,这个类看起来像这样:

public class MyClass: NSObject {
func method() {
// Do something...
// ...
SingletonClass.sharedInstance.callMethod()
}
}

该类使用一个单例实现如下:

public class SingletonClass: NSObject {

// Only accessible using singleton
static let sharedInstance = SingletonClass()

private override init() {
super.init()
}

func callMethod() {
// Do something crazy that shouldn't run under tests
}
}

现在进行测试。我想测试 method() 实际上做了它应该做的事情,但我不想调用 callMethod() 中的代码(因为它做了一些可怕的异步/网络/线程的东西不应该在 MyClass 的测试下运行,并且会使测试崩溃)。

所以我基本上想做的是:

SingletonClass = MockSingletonClass: SingletonClass {
override func callMethod() {
// Do nothing
}
let myObject = MyClass()
myObject.method()
// Check if tests passed

这显然不是有效的 Swift,但你明白了。我如何才能仅针对此特定测试覆盖 callMethod(),使其无害?

编辑: 我尝试使用依赖注入(inject)的形式解决这个问题,但遇到了大问题。我创建了一个自定义初始化方法,仅用于测试,这样我就可以像这样创建我的对象:

let myObject = MyClass(singleton: MockSingletonClass)

让 MyClass 看起来像这样

public class MyClass: NSObject {
let singleton: SingletonClass

init(mockSingleton: SingletonClass){
self.singleton = mockSingleton
}

init() {
singleton = SingletonClass.sharedInstance
}

func method() {
// Do something...
// ...
singleton.callMethod()
}
}

将测试代码与其余代码混合是我觉得有点不愉快的事情,但没关系。最大的问题是我在我的项目中有两个这样构造的单例,它们都相互引用:

public class FirstSingletonClass: NSObject {
// Only accessible using singleton
static let sharedInstance = FirstSingletonClass()

let secondSingleton: SecondSingletonClass

init(mockSingleton: SecondSingletonClass){
self.secondSingleton = mockSingleton
}

private override init() {
secondSingleton = SecondSingletonClass.sharedInstance
super.init()
}

func someMethod(){
// Use secondSingleton
}
}

public class SecondSingletonClass: NSObject {
// Only accessible using singleton
static let sharedInstance = SecondSingletonClass()

let firstSingleton: FirstSingletonClass

init(mockSingleton: FirstSingletonClass){
self.firstSingleton = mockSingleton
}

private override init() {
firstSingleton = FirstSingletonClass.sharedInstance
super.init()
}

func someOtherMethod(){
// Use firstSingleton
}
}

这会在第一次使用其中一个单例时造成死锁,因为 init 方法将等待另一个的 init 方法,依此类推...

最佳答案

您的单例遵循 Swift/Objective-C 代码库中非常常见的模式。正如您所见,它也很难测试,并且是编写不可测试代码的一种简单方法。有时单例模式是一种有用的模式,但根据我的经验,该模式的大多数用途实际上并不适合应用程序的需求。

来自 Objective-C 的 +shared_ 风格单例和 Swift 类常量单例通常提供两种行为:

  1. 它可能强制只能实例化一个类的单个实例。 (在实践中,这通常不会强制执行,您可以继续 alloc/init 额外的实例,而应用程序依赖于开发人员遵循通过独占访问共享实例的约定类方法。)
  2. 它充当全局变量,允许访问类的共享实例。

行为 #1 偶尔有用,而行为 #2 只是具有设计模式文凭的全局行为。

我会通过完全删除全局变量来解决您的冲突。始终注入(inject)您的依赖项,而不仅仅是为了测试,并考虑当您需要一些东西来协调您正在注入(inject)的任何共享资源集时,您的应用程序中暴露的责任。

在整个应用程序中注入(inject)依赖项的第一步通常很痛苦; “但我到处都需要这个实例!​​”。使用它作为重新考虑设计的提示,为什么这么多组件访问相同的全局状态以及如何对其建模以提供更好的隔离?


在某些情况下,您需要一些可变共享状态的单个副本,而单例实例可能是最好的实现。但是我发现在大多数例子中仍然不成立。开发人员通常在寻找共享状态,但有一些条件:在连接外部显示器之前只有一个屏幕,只有一个用户在他们注销并进入第二个帐户之前只有一个网络请求队列,在您发现需要进行身份验证之前只有一个网络请求队列vs 匿名请求。同样,在执行下一个测试用例之前,您通常需要一个共享实例。

考虑到使用可失败初始化器(或返回现有共享实例的 obj-c init 方法)的“单例”似乎很少,开发人员似乎很乐意按照惯例共享此状态,所以我认为没有理由不注入(inject)共享对象并编写易于测试的类,而不是使用全局变量。

关于swift - 在 Swift 中模拟单例/sharedInstance,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35094734/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com