Как я могу проверить, что метод класса вызывается с использованием XCTAssert? - PullRequest
0 голосов
/ 01 января 2019

У меня есть класс обслуживания, я хотел бы заявить 2 вещи

  1. Метод называется
  2. В этот метод передаются правильные параметры

Вот мой класс

protocol OAuthServiceProtocol {
    func initAuthCodeFlow() -> Void
     func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void
}

class OAuthService: OAuthServiceProtocol {

    fileprivate let apiClient: APIClient

    init(apiClient: APIClient) {
        self.apiClient = apiClient
    }

    func initAuthCodeFlow() -> Void {

    }

    func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void {

    }
}

Вот мои тесты

class OAuthServiceTests: XCTestCase {
    var mockAPIClient: APIClient!
    var mockURLSession: MockURLSession!
    var sut: OAuthService!

    override func setUp() {
        mockAPIClient = APIClient()
        mockAPIClient.session = MockURLSession(data: nil, urlResponse: nil, error: nil)
        sut = OAuthService(apiClient: mockAPIClient)
    }

    func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
        let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")

        class OAuthServiceMock: OAuthService {
            override func initAuthCodeFlow() -> Void {

            }

            override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
                renderOAuthWebViewExpectation.fulfill()
            }
        }
    }
}

Я надеялся создать локальный подкласс OAuthService, назначить его как мой sut и вызвать что-токак, например, sut.initAuthCodeFlow() и затем утверждают, что мое ожидание было выполнено.

Я считаю, что это должно удовлетворять пункт 1. Однако я не могу получить доступ к своему ожиданию при попытке назначить его как выполненное, поскольку я получаю следующую ошибку

Объявление класса не может закрыться по значению 'renderOAuthWebViewExpectation', определенному во внешней области действия

Как пометить это как выполненное?

Я придерживаюсь подхода TDD, поэтомуЯ понимаю, что мой OAuthService в любом случае произведет неудачный тест *

Ответы [ 2 ]

0 голосов
/ 15 февраля 2019

Я надеялся создать локальный подкласс OAuthService, назначить его в качестве моего sut и назвать что-то вроде sut.initAuthCodeFlow(), а затем утверждать, что мои ожидания оправдались.

Я бы настоятельно не рекомендовал вам использовать этот подход.Если ваша SUT является экземпляром подкласса, тогда ваш тест на самом деле не тестирует OAuthService, но OAuthService макет.

Более того, если мы думаем о тестах как о инструменте для:

  • предотвращение ошибок при изменении кода
  • помогает рефакторинг и обслуживание кода

тогда я бы сказал, что тестирование того, что вызов определенной функции вызывает другую функцию, не является хорошим тестом,Это жестко, я знаю, поэтому позвольте мне распаковать, почему это так.

Единственное, что он проверяет, это то, что initAuthCodeFlow() вызывает renderOAuthWebView(forService:, queryitems:) под капотом.Он не имеет никакого утверждения о фактическом поведении тестируемой системы, о выходных данных, которые он производит напрямую или нет.Если бы мне пришлось отредактировать реализацию renderOAuthWebView(forService:, queryitems:) и добавить некоторый код, который мог бы вылетать во время выполнения, этот тест не провалился бы.

Такой тест не поможет сохранить легкость изменения кодовой базы, потому что еслиВы хотите изменить реализацию OAuthService, возможно, добавив параметр в renderOAuthWebView(forService:, queryitems:) или переименовав queryitems в queryItems, чтобы он соответствовал заглавным буквам, вам придется обновить как рабочий код, так и тест.Другими словами, тест помешает вам выполнить рефакторинг - изменение внешнего вида кода без изменения его поведения - без каких-либо дополнительных преимуществ.

Итак, как следует тестировать OAuthService таким образом, чтобы предотвратитьошибки и помогает двигаться быстро?Вся хитрость заключается в тестировании поведения вместо реализации.

Что должен OAuthService делать ?initAuthCodeFlow() не возвращает никакого значения, поэтому мы можем проверять прямые выходы, но мы все равно можем проверять косвенные выходы, побочные эффекты.

Я угадаю здесь, но я из вашего теста проверяю, чтоrenderOAuthWebView(forService:, queryitems:) Я бы и тот факт, что он получает тип APIClient в качестве входных данных, я бы сказал, что он представит какое-то веб-представление для определенного URL, а затем сделаю еще один запрос к данному APIClient, возможно, смаркер OAuth, полученный из веб-представления?

Чтобы проверить взаимодействие с APIClient, нужно сделать утверждение для ожидаемой конечной точки, которая будет вызвана.Вы можете сделать это с помощью инструмента, такого как OHHTTPubs или с вашим пользовательским двойным тестом для URLSession, который записывает полученные запросы и позволяет вам проверять их.

Что касается представленияВ веб-представлении вы можете использовать для него шаблон делегата и установить двойной тест, соответствующий протоколу делегата, который записывает, вызван он или нет.Или вы можете протестировать на более высоком уровне и проверить UIWindow, в котором выполняется тест, чтобы увидеть, является ли контроллер корневого представления тем, у которого есть веб-представление.

В конце дня всевопрос компромиссов.Подход, который вы выбрали, не является неправильным, он просто больше оптимизирует утверждение кода, а не его поведение.Я надеюсь, что с этим ответом я продемонстрировал другой вид оптимизации, смещенный в сторону поведения.По моему опыту, этот стиль тестирования оказывается более полезным в среднесрочной перспективе.

0 голосов
/ 01 января 2019

Создайте свойство на макете, изменив его значение в методе, который вы ожидаете вызвать.Затем вы можете использовать XCTAssertEqual, чтобы проверить, что реквизит обновлен.

   func test_InitAuthCodeFlow_CallsRenderOAuthWebView() {
        let renderOAuthWebViewExpectation = expectation(description: "RenderOAuthWebView")

        class OAuthServiceMock: OAuthService {
            var renderOAuthWebViewExpectation: XCTestExpectation!
            var didCallRenderOAuthWebView = false

            override func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) {
                didCallRenderOAuthWebView = true
                renderOAuthWebViewExpectation.fulfill()
            }
        }

        let sut = OAuthServiceMock(apiClient: mockAPIClient)

        XCTAssertEqual(sut.didCallRenderOAuthWebView, false)
        sut.renderOAuthWebViewExpectation = renderOAuthWebViewExpectation

        sut.initAuthCodeFlow()
        waitForExpectations(timeout: 1) { _ in
            XCTAssertEqual(sut.didCallRenderOAuthWebView, true)
        }

    }
...