Как в Xcode 8 заставить тестовые методы запускаться в определенном порядке в заданном классе XCTestCase?

До Xcode 8 вы могли запускать модульные тесты в определенном порядке, упорядочив имена тестовых методов в заданном классе XCTestCase в алфавитном порядке (как описано в этот ответ). Например, тесты будут выполняться так: testA, testB, testC, testD и т. д.

Однако в Xcode 8 это уже не так. Например, у меня есть тестовые методы с именами test1, test2, test3, test4 и test4, которые будут запускаться первыми (см. скриншот ниже). Затем я могу запустить его повторно, и test2 запустится первым при следующем прогоне.

Снимок экрана тестового навигатора Xcode

Итак, как мне заставить тесты работать по порядку на Xcode 8?


person CommaToast    schedule 20.09.2016    source источник
comment
Как сказано в комментариях к вопросам и ответам, на которые вы ссылались, предполагается, что модульные тесты могут выполняться независимо от любых других тестов — зачем вам их заказывать?   -  person Hamish    schedule 20.09.2016
comment
Это не модульные тесты, это последовательные тесты в сквозном тесте. Почему они обязательно должны быть модульными тестами? Не все тесты в этом мире являются юнит-тестами.   -  person CommaToast    schedule 21.09.2016
comment
В любом случае, на данный момент я превратил все это в один большой тест, используя наблюдатели и селекторы, блоки завершения и т. д. Вы обнаружите, что есть случаи, когда большие сквозные тесты могут быть очень ценными в дополнение к стандартным модульным тестам. Но если предполагается, что каждый метод XCTestCase представляет собой отдельный модуль без зависимостей от других тестов, то так оно и есть. Я просто не уверен, баг это или нет.   -  person CommaToast    schedule 21.09.2016
comment
См. раздел Программное создание тестов в developer.apple.com/reference/ xctest/xctestcase. Я бы не ожидал интеграции Xcode с этим подходом, но вы могли бы заставить что-то работать.   -  person Avi    schedule 21.09.2016
comment
@CommaToast, хорошие фреймворки модульного тестирования рандомизируют порядок тестов, чтобы убедиться, что в вашем коде нет странных зависимостей (хороший источник ошибок).   -  person zhon    schedule 21.09.2016
comment
@CommaToast, если вы приведете пример того, зачем вам нужны зависимости между тестами, мы можем показать вам, как писать тесты без зависимостей.   -  person zhon    schedule 21.09.2016
comment
У меня есть несколько тестов интеграции/UI, для запуска которых требуется некоторая настройка. В настоящее время у меня есть настройки в тестах, которые необходимо запускать в определенном порядке, т.е. войти в систему перед запуском тестов. Это изменение нарушит эти установочные тесты, поэтому мне нужно будет найти решение.   -  person Alex    schedule 21.09.2016
comment
@zhon У меня та же проблема, что и у Алекса. Делаем приложение для точек продаж. Тест имитирует, как пользователь открывает кассу, запускает набор билетов (транзакций) на основе данных в электронной таблице Excel, которые считываются в тест с помощью классной среды чтения файлов Excel, затем приложение синхронизирует все данные билетов с наш тестовый сервер, затем удаляет свои локальные копии и синхронизирует все данные обратно и убеждается, что они такие же, как до синхронизации, затем закрывает кассу и убеждается, что его математические расчеты по всем суммам верны. Очевидно, что многие из этих вещей также должны иметь модульные тесты.   -  person CommaToast    schedule 01.10.2016
comment
Но нам нравится сквозное тестирование, потому что оно имитирует то, как реальный пользователь на самом деле будет использовать приложение, и это было бесценно, поскольку помогло нам найти сложные проблемы, связанные с многопоточностью, когда несколько вещей происходят одновременно, это модульное тестирование просто никогда не мог видеть, потому что единицы - это вещи, которые находятся в своих собственных потоках, а модульные тесты не могут иметь дело с тем, что происходит в других потоках и т. д.   -  person CommaToast    schedule 01.10.2016
comment
Я нахожусь в той же лодке, где определенный набор тестов пользовательского интерфейса должен выполняться в определенной последовательности. Вы не можете попасть на целевую страницу, не обработав сначала страницу входа. Не имеет ничего общего с модульным тестированием, так как это сквозное. Я сейчас в растерянности и не хочу создавать 10 тестовых целей для проверки работоспособности.   -  person Laser Hawk    schedule 28.10.2016


Ответы (1)


Итак, я решил это следующим образом.

Краткий обзор проблемы:

Мне нужно было запустить несколько тестов подряд: test1, test2, test3, test4. Каждый тест устанавливал ожидание, и последний шаг в тесте соответствовал ожиданию, затем тест завершался, и запускался следующий.

Однако в Xcode 8 тесты теперь выполняются в случайном порядке. Хотя это хорошо с той точки зрения, что если это модульные тесты, то они должны выполняться в произвольном порядке, но ваши тесты ломаются, если они разработаны не как модульные тесты, а как end -до конца тестов.

Например, в моем случае это ломает тесты, потому что в первом тесте пользователь входит в систему и настраивает некоторые данные и т. д. Затем второй тест проверяет математику, затем третий тест синхронизирует данные с сервером, затем четвертый удаляет все это и синхронизируется с сервером. Когда запускается первый тест, во время сборки сценарий оболочки инициирует БД сервера из файла MSYQL, а затем во время запуска приложения AppDelegate устанавливает новую базу данных Core Data для приложения. Таким образом, если мне нужно запускать приложение заново после каждого теста, сценарий оболочки повторно инициализирует серверную БД и вызывает повторную инициализацию локальной базы данных Core Data DB. Это нарушит последующие тесты (это сквозной тест, последующие тесты зависят от состояния приложения и сервера определенным образом после запуска предыдущего теста).

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

Решение

Во-первых, в классе XCTestCase я установил свойство ожидания теста:

@property (nonatomic, strong) XCTestExpectation *endOfTestExpectation;

В конце test1 в моем классе XCTestCase я заменил его существующее ожидание этим новым ожиданием, например так:

self.endOfTestExpectation = [self expectationWithDescription:
                                  @"endOfTestExpectation"];

[self waitForExpectationsWithTimeout:900 
                             handler:^(NSError * _Nullable error) {
    /* Code moved from test4's expectation completion block goes here */
}

Для каждого с test1 по test3 я переместил код, который находился внутри исходного блока завершения теста, в новый метод, названный с completion1 по completion3. Для test4 я переместил код внутри исходного блока завершения ожидания в блок завершения endOfTestExpectation в конце метода test1.

Затем я переименовал методы с test2 по test4, назвав их с t3st2 по t3st4 (я знаю, быстро и грязно; вы должны выбрать что-то более описательное после того, как вы заработаете). В конце метода completion1 я вызываю t3st2; в конце completion2 я звоню t3st3; в конце completion3 я звоню t3st4; и в конце completion4 я звоню [self.endOfTestExpectation fulfill];.

Это на самом деле оказывается лучше, чем старый способ, потому что по-старому, даже если первый тест не пройден, последующие тесты все равно будут выполняться! Теперь, где бы ни происходило XCTFail, все просто останавливается, и мы не тратим время на прогон остальных, если меня перевели в SO :D

person CommaToast    schedule 22.09.2016
comment
Спасибо, что изучили это. Вы случайно не знаете, как это сделать в Swift? - person Laser Hawk; 26.10.2016
comment
По-старому вы могли использовать continueAfterFailure = true в функции переопределения setUp(), но в любом случае похоже, что теперь мне нужно использовать ожидание() : (больше кода - person Laser Hawk; 28.10.2016