如何对Swift中调用的私有方法进行单元测试

时间:2019-01-08 03:13:54

标签: swift xcode unit-testing uiviewcontroller xctest

我有一个ViewController类,该类提供了一系列两个选择的弹出视图。每个两个选择的弹出视图都不同。

Popup1-Choice1-> Choice1Popup

Popup1-Choice2-> Choice2Popup

我打算将Popup1呈现为公开的方法,但我希望将Choice1Popup和Choice2Popup呈现为其他的方法是私有的。

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

我想编写一个单元测试,该测试测试何时按下了显示Choice1Popup的方法的Choice1按钮。我使用了带有方法类型变量的协议,以允许Mock注入弹出演示者的Mock版本。我对自己的方法感到不满意100%,因此我想就是否有更好的方法获得意见。

顺便说一句,我感到内部与私人之间存在冲突。能够测试我的私有方法会很好,但是我不希望它们能够在除单元测试之外的任何地方被调用,并使它们在内部公开。

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

// 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")
        }
    }
}

1 个答案:

答案 0 :(得分:1)

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

当您感到不安时,“但是我不必暴露这一点”,请考虑您的类实际上具有多个接口。这里有“它所做的一切接口”,还有“生产代码接口所需的零件”。有很多事情要考虑:

  • 还有其他试图逃脱的类型吗?
  • 是否存在另一种协议来表达接口的子集?其余的生产代码都可以使用它。
  • 或者它就像是一个家庭影院放大器,其中“不需要经常使用的控件”隐藏在面板后面。不要流汗。