swift - 如何对在 Swift 中调用的私有(private)方法进行单元测试

标签 swift xcode unit-testing uiviewcontroller xctest

我有一个 ViewController 类,它提供了一系列有两种选择的弹出 View 。每两个选择弹出 View 都是不同的。

Popup1 - Choice1 -> Choice1Popup

Popup1 - Choice2 -> Choice2Popup

我希望呈现 Popup1 的方法是公开的,但我希望呈现 Choice1Popup 和 Choice2Popup 的其他方法是私有(private)的。

如果我决定我需要测试 Choice1Popup 和 Choice2Popup,那么我可能必须将它们设为内部而不是私有(private),但它们不太可能在任何其他地方使用。

我想编写一个单元测试来测试在触摸 Choice1 的按钮时调用显示 Choice1Popup 的方法。我使用了一个带有方法类型变量的协议(protocol)来允许模拟注入(inject)弹出演示者的模拟版本。我对我的方法不是 100% 满意,所以我想就是否有更好的方法征求意见。

顺便说一句,我对内部和私有(private)感到矛盾。能够测试我的私有(private)方法会很好,但我不希望它们能够从除单元测试之外的任何地方调用并使它们在内部公开。

这是代码,底部是一个单元测试:

// protocol to be used by both UserChoices class and UserChoicesMock for method injection
protocol UserChoicesPrivateUnitTesting {
    static var choice1Method:(UIViewController) -> Void { get set }
    static var choice2Method:(UIViewController) -> Void { get set }
}

// this popup that will be presented with a public method
public class ChoiceViewController:UIViewController {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var subjectLabel: UILabel!
    @IBOutlet weak var choice1Button: UIButton!
    @IBOutlet weak var choice2Button: UIButton!

     var choice1Action:(() -> Void)?
     var choice2Action:(() -> Void)?

    //    ...
}

public class UserChoices: UIViewController, UserChoicesPrivateUnitTesting {
    static var choice1Method: (UIViewController) -> Void = choice1
    static var choice2Method: (UIViewController) -> Void = choice2

    private static func choice1(onTopViewController: UIViewController) {
    //present choice1Popup
    }

    private static func choice2(onTopViewController: UIViewController) {
    //present choice2Popup
    }

    public static func presentChoiceViewController(onTopViewController: UIViewController, ChoiceViewController: ChoiceViewController = ChoiceViewController.instantiateFromAppStoryBoard(appStoryBoard: .MenuStoryboard)) {
        let isCustomAnimated = true
    //        ChoiceViewController.transitioningDelegate = transitionDelegate

        ChoiceViewController.choice1Action = { [weak onTopViewController]() in
            guard let weakSelf = onTopViewController else {
                return
            }
            weakSelf.dismiss(animated: false, completion: nil)
            UserChoices.choice1Method(onTopViewController!)
        }

        ChoiceViewController.choice2Action = { [weak onTopViewController]() in
            guard let weakSelf = onTopViewController else {
                return
            }
            weakSelf.dismiss(animated: false, completion: nil)
            UserChoices.choice2Method(onTopViewController!)
        }
        onTopViewController.present(ChoiceViewController, animated: isCustomAnimated, completion: nil)
    }
}

import XCTest
@testable import ChoiceModule

public class UserChoicesMock:UserChoicesPrivateUnitTesting {
    static public var choice1Method: (UIViewController) -> Void = choice1
    static public var choice2Method: (UIViewController) -> Void = choice2
    static var choice1MethodCalled = false
    static var choice2MethodCalled = false

    static func choice1(onTopViewController: UIViewController) {
        choice1MethodCalled = true
    }

    static func choice2(onTopViewController: UIViewController) {
        choice2MethodCalled = true
    }
}

class UserChoicesTests: XCTestCase {

    func testChoice1CallsPrivateChoice1Method() {
        // This is an example of a functional test case.
        let vc = UIViewController()
        let choiceViewController = ChoiceViewController.instantiateFromAppStoryBoard(appStoryBoard: .MenuStoryboard)

        UserChoices.choice1Method = UserChoicesMock.choice1Method

        UserChoices.presentChoiceViewController(onTopViewController: vc, ChoiceViewController: choiceViewController)

        choiceViewController.choice1Button.sendActions(for: .touchUpInside)

        if UserChoicesMock.choice1MethodCalled == false {
            XCTFail("choice1Method not called")
        }
    }
}

最佳答案

测试无法访问任何声明为 private 的内容。只要测试代码执行 @testable import,他们就可以访问声明为 internal 的任何内容。

当您有一种反胃的感觉时,“但我不应该公开这个”,请考虑您的类实际上有多个接口(interface)。有“它所做的一切接口(interface)”和“生产代码接口(interface)所需的部分”。有很多事情需要考虑:

  • 是否有另一种类型试图逃脱?
  • 是否有另一种协议(protocol)来表达接口(interface)的子集?这可以被其余的生产代码使用。
  • 或者它可能就像一个家庭影院放大器,其中“您并不经常需要的控件”隐藏在面板后面。不要担心。

关于swift - 如何对在 Swift 中调用的私有(private)方法进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54084767/

相关文章:

ios - 搜索栏正在刷新我的表格 View 单元格的索引

ios - Facebook 登录与 Swift 崩溃问题

objective-c - 如何在调试配置中启用 ARC 优化器?

ios - 您清理项目中图像资源的方法是什么

ios - 在所有 subview 自动布局之前调用 viewWillTransitionToSize

swift - 你需要在 Swift 中释放 CGContextRef 吗?

ios - "countByEnumeratingWithState:objects:count:"错误

javascript - Nodeunit 测试基于事件的异步代码

python - 使用模拟来修补不存在的属性

angular - 对使用可观察对象和异步管道的 Angular 2 组件进行单元测试