Тестирование порядка вызова нескольких методов - PullRequest
0 голосов
/ 07 февраля 2019

У меня есть 3 метода, которые относятся к определенному классу, который определяется следующим образом:

class MyClass: NSObject {
    func myMethod() {
        methodA()
        methodB()
        methodC()
    }
    func methodA() {}
    func methodB() {}
    func methodC() {}
}

Мне нужно проверить, что myMethod вызвал все 3 метода в порядке их реализации: methodA, затем methodB, затем methodC для тестирования с помощью модульных тестов XCode, независимо от реализации этих методов, я создал подкласс в тестовом примере, который выглядит следующим образом:

class ChildClass: MyClass {
    var method1CallingDate: Date?
    var method2CallingDate: Date?
    var method3CallingDate: Date?

    override func methodA() {
        super.methodA()
        method1CallingDate = Date()
    }
    override func methodB() {
        super.methodB()
        method2CallingDate = Date()
    }
    override func methodC() {
        super.methodC()
        method3CallingDate = Date()
    }
}

Теперь в тестовом методе я начинаю с вызова этих трех методов, затем утверждаю, что все три даты не равны нулю, а затем сравниваю их следующим образом:

XCTAssertLessThan(method1CallingDate, method2CallingDate)
XCTAssertLessThan(method2CallingDate, method3CallingDate)

Проблема, с которой я столкнулся, заключалась в том, что тестиногда успешно, а иногда и неудачно, я думаю, из-за того, что Date объект (случайно) одинаков между двумя вызовами метода.Есть ли лучший способ проверить порядок вызова нескольких методов?

ps это легко сделать в Android SDK org.mockito.Mockito.inOrder

Ответы [ 4 ]

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

Сначала создайте фиктивный объект, который записывает порядок.Нет дат, нет строк.Просто перечисление.

class MockMyClass: MyClass {
    enum invocation {
        case methodA
        case methodB
        case methodC
    }
    private var invocations: [invocation] = []

    override func methodA() {
        invocations.append(.methodA)
    }

    override func methodB() {
        invocations.append(.methodB)
    }

    override func methodC() {
        invocations.append(.methodC)
    }

    func verify(expectedInvocations: [invocation], file: StaticString = #file, line: UInt = #line) {
        if invocations != expectedInvocations {
            XCTFail("Expected \(expectedInvocations), but got \(invocations)", file: file, line: line)
        }
    }
}

Затем в тесте:

mock.verify(expectedInvocations: [.methodA, .methodB, .methodC])

Нет асинхронного ожидания.Просто позвонить.Очистить сообщения об ошибках.

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

Я стал поклонником использования XCTestExpectation для такого рода вещей.Вот вариант.

class MyTestableClass: MyClass {
    var methodAHandler: (() -> Void)?
    // ...

    override func methodA() {
        methodAHandler?()  
        super.methodA()
    }

А затем в вашем тестовом примере

   let expA = XCTestExpectation(description: "Method A Called")
   let expB = ...
   let expo = ...

   objectUnderTest.methodAHandler = { expA.fulfill() }
   /// ...

   objectUnderTest.myMethod()

   // ensure you use the enforceOrder param, which is optional
   wait(for: [expA, expB, expC], timeout: 1.0, enforceOrder: true)

XCTestExpectation больше подходит для асинхронного тестирования, поэтому ожидание немного забавно.Но он делает то, что вам нужно, и продолжит работать, даже если в конечном итоге внутренности myMethod по какой-то причине станут асинхронными.

Хотя я сам не использовал его, вы также можете захотеть проверить Кукушка .Это насмешливая основа для Swift.

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

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

Модульные тесты должны подтверждать некоторые наблюдаемые результаты тестируемого метода.Все, что происходит внутри тестируемого метода, не имеет значения в контексте модульного теста.

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

Давайте рассмотрим простой пример, модульное тестирование функции isPrime(n).Если вы не проводите тестирование производительности, вам нужно только, чтобы функция возвращала соответствующий результат для пары чисел.Вам все равно, проверяет ли функция все возможные делители, использует ли она базу данных всех известных простых чисел или делегирует первичную проверку какой-либо сторонней библиотеке / службе.

Ситуация не оченьотличается от твоего.Тот факт, что три метода вызываются в определенном порядке, должен быть подтвержден через внешний интерфейс тестируемого устройства.Например, если три метода совершают вызовы API, затем смоделируйте клиент API и ожидайте, что он будет запрошен три раза, и с ожидаемым URL / полезной нагрузкой.Если вызов этих трех методов не приводит к каким-либо заметным изменениям, то с самого начала вы можете протестировать немногое, поэтому снова тот факт, что три метода вызываются в определенном порядке, становится неактуальным.

Модульное тестированиео проверке результата выполнения этого блока, а не о чем-то большем.Теперь в императивном языке программирования функции input-> output составляют меньшинство, однако это не означает, что мы не можем косвенно проверить, работает ли функция, как ожидалось.Вы можете использовать макеты или проверить некоторые свойства объекта после выполнения функции.Опять же, если нет способов внешней проверки порядка методов, то у вас нет спецификации для проверки.

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

Вы можете сделать что-то подобное, используя String для отслеживания порядка:

class ChildClass: MyClass {
    var order = ""

    override func methodA() {
        super.methodA()
        order = String((order + "A").suffix(3))
    }
    override func methodB() {
        super.methodB()
        order = String((order + "B").suffix(3))
    }
    override func methodC() {
        super.methodC()
        order = String((order + "C").suffix(3))
    }
}

Затем просто убедитесь, что order равно "ABC".


Или, если допустимо многократно вызывать B между A и C:

class ChildClass: MyClass {
    var order = ""

    override func methodA() {
        super.methodA()
        order = order.replacingOccurrences(of: "A", with: "") + "A"
    }
    override func methodB() {
        super.methodB()
        order = order.replacingOccurrences(of: "B", with: "") + "B"
    }
    override func methodC() {
        super.methodC()
        order = order.replacingOccurrences(of: "C", with: "") + "C"
    }
}

Пример:

let c = ChildClass()
c.methodA()
c.methodB()
c.methodB()
c.methodC()

print(c.order)
ABC
...