Подтвердить что ты не робот

Как запустить единовременный код установки перед выполнением любого XCTest

У меня есть следующая проблема. Я хочу выполнить часть кода, прежде чем все тестовые классы будут выполнены. Например: я не хочу, чтобы моя игра использовала сингл SoundEngine во время выполнения, но SilentSoundEngine. Я хотел бы активировать SilentSoundEngine один раз не во всех тестах. Все мои тесты выглядят так:

class TestBasketExcercise : XCTestCase {        
    override func setUp() {
        SilentSoundEngine.activate () // SoundEngine is a singleton
    }
    // The tests 
}

-Edit- Большинство ответов направлено на предоставление пользовательского суперкласса для TestCase. Я ищу более общий и более чистый способ обеспечения среды, которую все тесты должны выполнять. Разве нет "основной" функции /Appdelegate как функция где-то для тестов?

4b9b3361

Ответ 1

TL; DR:
Как указано здесь, вы должны объявить NSPrincipalClass в своих тестовых объектах Info.plist. Выполните весь одноразовый код внутри init этого класса, так как "XCTest автоматически создает один экземпляр этого класса при загрузке тестового пакета", поэтому весь ваш одноразовый код будет выполняться один раз при загрузке тестовый комплект.


Немного более подробный:

Сначала ответьте на идею в своем редактировании:

Afaik, для тестового пакета нет main(), так как тесты вводятся в вашу основную цель, поэтому вам нужно добавить одноразовый код в main() вашей основной цели, время компиляции (или, по крайней мере, время выполнения) проверяет, используется ли цель для запуска тестов. Без этой проверки вы рискуете активировать SilentSoundEngine при нормальной работе цели, что, по моему мнению, нежелательно, так как название класса подразумевает, что этот звуковой движок не произведет никакого звука и честно, кто этого хочет?:)

Однако есть функция AppDelegate -like, я приду к этому в конце моего ответа (если вы нетерпеливы, это под заголовком "Другой (более XCTest-специфический) подход" ).


Теперь разделите этот вопрос на две основные проблемы:

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

Относительно точки 1:

Как @Martin R правильно упомянул в своих комментариях этот ответ к вашему вопросу, переопределение +load больше не возможно с Swift 1.2 (что является древней историей к настоящему времени: D) и dispatch_once() больше не доступно в Swift 3.


Один подход

При попытке использовать dispatch_once в любом случае Xcode ( >= 8) всегда очень умный и предполагает, что вместо этого вы должны использовать лениво инициализированные глобальные переменные. Конечно, термин global имеет тенденцию к тому, что все предаются страху и панике, но вы, конечно, можете ограничить их область действия, сделав их private/fileprivate (что делает то же самое для деклараций на уровне файлов), поэтому вы не загрязняете ваше пространство имен.

Имхо, они на самом деле довольно хороший образец (все же, доза делает яд...), который может выглядеть так, например:

private let _doSomethingOneTimeThatDoesNotReturnAResult: Void = {
    print("This will be done one time. It doesn't return a result.")
}()

private let _doSomethingOneTimeThatDoesReturnAResult: String = {
    print("This will be done one time. It returns a result.")
    return "result"
}()

for i in 0...5 {
    print(i)
    _doSomethingOneTimeThatDoesNotReturnAResult
    print(_doSomethingOneTimeThatDoesReturnAResult)
}

Отпечатки:

Это будет сделано один раз. Это не возвращает результат.
Это будет сделано один раз. Он возвращает результат.
0
результат
1
результат
2
результат
3
результат
4
результат
5
результат

Боковое примечание: Интересно, что частные даты оцениваются до того, как цикл даже начинается, что вы можете видеть, потому что, если бы это было не так, то 0 был бы самым первым шрифтом. Когда вы прокомментируете выход цикла, он все равно будет печатать первые две строки (т.е. Оценивать пробелы).
Тем не менее, я думаю, что это специфическое поведение на игровой площадке, потому что, как указано здесь и здесь, globals обычно инициализируются при первом обращении к ним, поэтому их не следует оценивать, когда вы закомментируете цикл.


Другой (более специфичный для XCTest) подход

(Это фактически решает обе точки 1 и 2...)

Поскольку компания из Купертино заявляет здесь, существует способ запуска кода установки с одним предварительным тестированием.

Чтобы достичь этого, вы создаете фиктивный установочный класс (может быть, назовите его TestSetup?) и поместите все одноразовый код установки в его init:

class TestSetup: NSObject {
    override init() {
        SilentSoundEngine.activate()
    }
}

Примечание, что класс должен наследовать от NSObject, так как Xcode пытается создать экземпляр "одного экземпляра этого класса" с помощью +new, поэтому, если класс является чистым классом Swift, это произойдет:

*** NSForwarding: warning: object 0x11c2d01e0 of class 'YourTestTargetsName.TestSetup' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector +[YourTestTargetsName.TestSetup new]

Затем вы объявляете этот класс в качестве PrincipalClass в тестовом пакете Info.plist: Объявление PrincipalClass в Info.plist

Примечаниечто вы должны использовать полное имя класса (т.е. YourTestTargetsName.TestSetup по сравнению с просто TestSetup), поэтому класс найден по Xcode (Спасибо, zneak...).

Как указано в документации XCTestObservationCenter, "XCTest автоматически создает один экземпляр этого класса, когда загружается тестовый комплект", поэтому весь ваш одноразовый код будет выполнен в init TestSetup при загрузке теста -расслоение.

Ответ 2

От Написание тестовых классов и методов:

Вы можете дополнительно добавить настраиваемые методы для настройки класса   (+ (void)setUp) и teardown (+ (void)tearDown), которые выполняются до и после всех методов тестирования в классе.

В Swift это будут методы class:

override class func setUp() {
    super.setUp()
    // Called once before all tests are run
}

override class func tearDown() {
    // Called once after all tests are run
    super.tearDown()
}

Ответ 3

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

// superclass
class SuperClass : XCTestCase {        
    override func setUp() {
        SilentSoundEngine.activate () // SoundEngine is a singleton
    }
}

// subclass
class Subclass : Superclass {
    override func setUp() {
        super.setup()
    }
}

Ответ 4

на самом деле не получается, но если вы ищете метод, который запускается только один раз... -Интернет хорошо или что определенно работает + load

EDIT:

поскольку вы больше не можете переопределять загрузку, если init не то, что вам нужно, используйте dispatch_once в init

Ответ 5

Если вы хотите вызывать метод setUp только один раз для всех тестов пользовательского интерфейса в классе, в XCode 11 вы можете вызвать override class func setUp()

Этот подход является частью документации Apple по тестированию пользовательского интерфейса:https://developer.apple.com/documentation/xctest/xctestcase/understanding_setup_and_teardown_for_test_methods