在 Swift 中为 class 扩展编写 unit-test

Writing a unit-test for a class extension in Swift

我正在尝试为 Swift 中的 class 扩展编写单元测试。 class 扩展本身将显示一个 UIAlert,其中包含指定的标题和消息:

extension UIViewController {

    func presentAlert(title: String, message : String) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
            alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.Default, handler: nil))

        UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

    }
}

我为我的单元测试创​​建了一个包含以下代码的文件:

import XCTest

class AlertTest: XCTestCase {

    func testAlert() {

        let alert = presentAlert("Presented Alert", "This is an Alert!")

    }

}

但是,我一直收到 "Use of unresolved identifier 'presentAlert'" 的错误。在咨询了 SO thread:

之后,我尝试将 public 添加到我的扩展程序中

public func presentAlert(title: String, message : String)

但仍然没有运气。有人有一些见解吗?

编辑

根据@hkgumbs 的回答,这是我的警报扩展的当前代码:

import Foundation

protocol Presentable {}

extension UIViewController {

    public func presentAlert(title: String, message : String) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
            alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.Default, handler: nil))

        UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

    }
}

在我想显示警报的视图控制器中,这仍然是调用我的警报的正确方式,对吗?

self.presentAlert("Invalid URL", message: "Please try again")

其次,根据您的评论,这就是我对虚拟值调用 Presentable 的理解,但这是不正确的,因为 SomethingPresentable 没有成员 PresentAlert。我的理解哪里出错了?

func testAlert() {

    let app = XCUIApplication()

    struct SomethingPresentable: Presentable {}

    SomethingPresentable.presentAlert("Presented Alert", message: "This is an Alert!")

    XCTAssert(app.alerts["Presented Alert"].exists)
    app.alerts["Presented Alert"].tap();

}

编辑 2 @hkgumbs,根据你的最新评论,这就是我的扩展:

import Foundation

protocol Presentable {}

extension Presentable {

    func presentAlert(title: String, message : String) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
            alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.Default, handler: nil))

        UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

    }
}

这就是我试图从我的 ViewController 中调用它的方式:

Presentable.presentAlert("Invalid URL", message: "Please try again")

但是,我收到 "Use of instance member presentAlert on type Self; did you mean to use a variable of type Self instead?"

的错误

那么,我猜这就是测试的样子?

func testAlert() {

    let app = XCUIApplication()

    struct SomethingPresentable: Presentable {}

    SomethingPresentable.presentAlert("Presented Alert", message: "This is an Alert!")

    XCTAssert(app.alerts["Presented Alert"].exists)
    app.alerts["Presented Alert"].tap();

}

正如 @dan 提到的,您需要从 UIViewController 实例 调用它。如果可以避免,通常您不想在测试中实例化框架对象,因此这里有一些选项可以避免这种情况:

  1. 使 presentAlert 静态,这样你就可以 UIViewController.presentAlert
  2. 使presentAlert成为一个自由函数(不要放在扩展中)
  3. 改为扩展协议——我认为这是最干净的选择

protocol Presentable {}

extension Presentable {
    func presentAlert(title: String, message : String) { /* ... */ }
}

然后,只要你需要,你就可以extension UIViewController: Presentable {}。在你的测试中,你可以只使用一个虚拟 class。这种方法的额外好处是,如果需要,您可以在任何类型上重用该函数,而无需在不需要时全局公开它。

附录

当我们扩展我们说的协议时 "anything that implements this protocol will get this method for free." 这里的技巧是这个协议是空的,因此很容易 "implement."

extension YourViewController: Presentable {}