gpt4 book ai didi

ios - 如何在 iOS 中使用委托(delegate)模拟外部框架类?

转载 作者:行者123 更新时间:2023-12-01 15:57:18 25 4
gpt4 key购买 nike

我在一个名为 ConnectApp 的 iOS 应用程序中工作。我正在使用一个名为 Connector 的框架.现在,Connector框架完成与BLE设备的实际连接任务,并让我的调用者应用程序(即ConnectApp)通过ConnectionDelegate知道连接请求结果.让我们看看示例代码,
ConnectApp - 主机应用程序

class ConnectionService: ConnectionDelegate {

func connect(){
var connector = Connector()
connector.setDelegate(self)
connector.connect()
}

func onConnected(result: ConnectionResult) {
//connection result
}
}
连接器框架
public class ConnectionResult {
// many complicated custom variables
}

public protocol ConnectionDelegate {
func onConnected(result: ConnectionResult)
}

public class Connector {

var delegate: ConnectionDelegate?

func setDelegate(delegate: ConnectionDelegate) {
self.delegate = delegate
}

func connect() {
//…..
// result = prepared from framework
delegate?.onConnected(result)
}
}
问题
有时开发人员没有 BLE 设备,我们需要模拟框架的连接器层。对于简单的类(即使用更简单的方法),我们可以使用继承并模拟 ConnectorMockConnector这可能会覆盖较低的任务并从 MockConnector 返回状态类(class)。但是当我需要处理一个 ConnectionDelegate它返回复杂的对象。我该如何解决这个问题?
请注意,框架不提供类的接口(interface),而是我们需要为具体对象找到方法,例如 Connector , ConnectionDelegate等等
更新1:
试图应用 Skwiggs 的答案,所以我创建了类似的协议(protocol),
protocol ConnectorProtocol: Connector {
associatedType MockResult: ConnectionResult
}
然后使用策略模式注入(inject)真实/模拟,例如,
class ConnectionService: ConnectionDelegate {

var connector: ConnectorProtocol? // Getting compiler error
init(conn: ConnectorProtocol){
connector = conn
}

func connect(){
connector.setDelegate(self)
connector.connect()
}

func onConnected(result: ConnectionResult) {
//connection result
}
}
现在我收到编译器错误,

Protocol 'ConnectorProtocol' can only be used as a generic constraint because it has Self or associated type requirements


我究竟做错了什么?

最佳答案

在 Swift 中,创建 Seam(一种允许我们替换不同实现的分离)最简洁的方法是定义一个协议(protocol)。这需要更改生产代码以与协议(protocol)对话,而不是像 Connector() 这样的硬编码依赖项。 .
首先,创建协议(protocol)。 Swift 允许我们将新协议(protocol)附加到现有类型。

protocol ConnectorProtocol {}

extension Connector: ConnectorProtocol {}
这定义了一个协议(protocol),最初是空的。它说 Connector符合这个协议(protocol)。
什么属于协议(protocol)?您可以通过更改 var connector 的类型来发现这一点。来自隐含的 Connector到明确的 ConnectorProtocol :
var connector: ConnectorProtocol = Connector()
Xcode 会提示未知方法。通过将所需的每个方法的签名复制到协议(protocol)中来满足它。从您的代码示例来看,可能是:
protocol ConnectorProtocol {
func setDelegate(delegate: ConnectionDelegate)
func connect()
}
因为 Connector已经实现了这些方法,协议(protocol)扩展是满意的。
接下来,我们需要一种方法让生产代码使用 Connector ,但用于测试代码来替代协议(protocol)的不同实现。由于 ConnectionServiceconnect() 时创建一个新实例被调用,我们可以使用一个闭包作为一个简单的工厂方法。生产代码可以提供一个默认的闭包(创建一个 Connector ),就像一个闭包属性:
private let makeConnector: () -> ConnectorProtocol
通过将参数传递给初始化程序来设置其值。初始化器可以指定一个默认值,这样它就可以生成一个真正的 Connector除非另有说明:
init(makeConnector: (() -> ConnectorProtocol) = { Connector() }) {
self.makeConnector = makeConnector
super.init()
}
connect() , 调用 makeConnector()而不是 Connector() .由于我们没有针对此更改进行单元测试,因此请进行手动测试以确认我们没有破坏任何内容。
现在我们的 Seam 就位了,所以我们可以开始编写测试了。有两种类型的测试要编写:
  • 我们调用 Connector正确吗?
  • 调用委托(delegate)方法时会发生什么?

  • 让我们制作一个模拟对象来检查第一部分。我们调用 setDelegate(delegate:) 很重要。在调用 connect() 之前,所以让我们在一个数组中模拟记录所有调用。该数组为我们提供了一种检查调用顺序的方法。与其让测试代码检查调用数组(充当仅记录内容的测试 spy ),如果我们将其设为成熟的 Mock 对象,您的测试将更加清晰——这意味着它将进行自己的验证。
    final class MockConnector: ConnectorProtocol {
    private enum Methods {
    case setDelegate(ConnectionDelegate)
    case connect
    }

    private var calls: [Methods] = []

    func setDelegate(delegate: ConnectionDelegate) {
    calls.append(.setDelegate(delegate))
    }

    func connect() {
    calls.append(.connect)
    }

    func verifySetDelegateThenConnect(
    expectedDelegate: ConnectionDelegate,
    file: StaticString = #file,
    line: UInt = #line
    ) {
    if calls.count != 2 {
    fail(file: file, line: line)
    return
    }
    guard case let .setDelegate(delegate) = calls[0] else {
    fail(file: file, line: line)
    return
    }
    guard case .connect = calls[1] else {
    fail(file: file, line: line)
    return
    }
    if expectedDelegate !== delegate {
    XCTFail(
    "Expected setDelegate(delegate:) with \(expectedDelegate), but was \(delegate)",
    file: file,
    line: line
    )
    }
    }

    private func fail(file: StaticString, line: UInt) {
    XCTFail("Expected setDelegate(delegate:) followed by connect(), but was \(calls)", file: file, line: line)
    }
    }
    (传递 fileline 的业务?这使得任何测试失败都会报告调用 verifySetDelegateThenConnect(expectedDelegate:) 的行,而不是调用 XCTFail(_) 的行。)
    以下是您在 ConnectionServiceTests 中的使用方法:
    func test_connect_shouldMakeConnectorSettingSelfAsDelegateThenConnecting() {
    let mockConnector = MockConnector()
    let service = ConnectionService(makeConnector: { mockConnector })

    service.connect()

    mockConnector.verifySetDelegateThenConnect(expectedDelegate: service)
    }
    这负责第一种类型的测试。对于第二种类型,无需测试 Connector调用代表。你知道它确实如此,而且它不在你的控制范围内。相反,编写一个测试来直接调用委托(delegate)方法。 (您仍然希望它发出 MockConnector 以防止对真正的 Connector 进行任何调用)。
    func test_onConnected_withCertainResult_shouldDoSomething() {
    let service = ConnectionService(makeConnector: { MockConnector() })
    let result = ConnectionResult(…) // Whatever you need

    service.onConnected(result: result)

    // Whatever you want to verify
    }

    关于ios - 如何在 iOS 中使用委托(delegate)模拟外部框架类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59986791/

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