Как выполнить модульное тестирование этого частного метода в Swift - PullRequest
0 голосов
/ 08 января 2019

У меня есть класс ViewController, который представляет серию из двух всплывающих окон. Каждое всплывающее окно с двумя вариантами выбора отличается.

Popup1 - Choice1 -> Choice1Popup

Popup1 - Choice2 -> Choice2Popup

Я хочу, чтобы метод представлял Popup1 как общедоступный, но я хочу, чтобы другие методы, которые представляют Choice1Popup и Choice2Popup, были частными.

Если я решу, что мне нужно протестировать Choice1Popup и Choice2Popup, то мне, возможно, придется сделать их внутренними, а не частными, но вряд ли они когда-либо будут использоваться из любого другого места.

Я хочу написать модульный тест, который проверяет, когда при нажатии кнопки для Choice1 вызывается метод, представляющий Choice1Popup. Я использовал протокол с переменными типа метода, чтобы позволить 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 голосов
/ 10 января 2019

Тесты не могут получить доступ к чему-либо объявленному private. Они могут получить доступ ко всему объявленному internal, пока тестовый код делает @testable import.

Когда у вас возникает тошнотворное чувство «Но я не должен был это раскрывать», подумайте, что ваш класс на самом деле имеет несколько интерфейсов. Есть «все, что он делает интерфейс», и есть «части, необходимые для интерфейса производственного кода». Есть несколько вещей, которые следует учитывать по этому поводу:

  • Есть другой тип, который пытается выбраться?
  • Есть ли другой протокол для выражения подмножества интерфейса? Это может быть использовано остальной частью производственного кода.
  • Или, может быть, это как усилитель для домашнего кинотеатра, где «элементы управления, которые вам не нужны часто» скрыты за панелью. Не парься.
...