Как сообщить приложению, работает ли его модульные тесты в чистом проекте Swift?


одна неприятная вещь при запуске тестов в XCode 6.1 заключается в том, что все приложение должно запускать и запускать свою раскадровку и корневой viewController. В моем приложении это запускает некоторые вызовы сервера, который извлекает данные API. Однако я не хочу, чтобы приложение делало это при запуске своих тестов.

когда макросы препроцессора исчезли, что лучше для моего проекта, чтобы знать, что он был запущен с запуском тестов, а не обычным запуском? Я запускаю их обычно с CMD+U и на боте.

псевдо код будет такой:

// Appdelegate.swift

if runningTests() {
   return
} else {
   // do ordinary api calls
}
10 51

10 ответов:

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

enter image description here

ответ Элвинда не плох, если вы хотите иметь то, что раньше называлось чистыми "логическими тестами". Если вы все еще хотите запустить свое содержащее хост-приложение, но условно выполнить или не выполнить код в зависимости от того, выполняются ли тесты, вы можете использовать следующее, Чтобы определить, был ли введен тестовый пакет:

if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
     // Code only executes when tests are running
}

я использовал флаг условной компиляции, как описано в ответ так, что во время выполнения затраты, понесенные при отладке строит:

#if DEBUG
    if NSProcessInfo.processInfo().environment["XCTestConfigurationFilePath"] != nil {
        // Code only executes when tests are running
    }
#endif

Редактировать Swift 3.0

if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil {
    // Code only executes when tests are running
}

Я использую это в приложении: didFinishLaunchingWithOptions:

// Return if this is a unit test
if let _ = NSClassFromString("XCTest") {
    return true
}

Я считаю, что это совершенно законно хотят знать, если вы работаете внутри теста или нет. Есть много причин, почему это может быть полезно. Например, при выполнении тестов я возвращаюсь рано из методов application-did/will-finish-Launch в делегате приложения, что ускоряет запуск тестов для кода, не относящегося к моему модульному тесту. Тем не менее, я не могу пройти чистый "логический" тест по множеству других причин.

я использовал отличную технику, описанную @Michael McGuire выше. Однако я заметил, что перестал работать для меня вокруг Xcode 6.4/iOS8.4.1 (возможно, он сломался раньше).

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

Итак, используя подход @Fogmeister, каждая из моих тестовых схем теперь устанавливает переменную среды, которую я могу проверить для.

enter image description here

тогда, вот какой код у меня есть на класс под названием APPSTargetConfiguration что может ответить на этот простой вопрос для меня.

static NSNumber *__isRunningTests;

+ (BOOL)isRunningTests;
{
    if (!__isRunningTests) {
        NSDictionary *environment = [[NSProcessInfo processInfo] environment];
        NSString *isRunningTestsValue = environment[@"APPS_IS_RUNNING_TEST"];
        __isRunningTests = @([isRunningTestsValue isEqualToString:@"YES"]);
    }

    return [__isRunningTests boolValue];
}

одно предостережение с этим подходом заключается в том, что если вы запустите тест из своей основной схемы приложения, как это позволит вам сделать XCTest (то есть не выбрав одну из своих схем тестирования), вы не получите этот набор переменных среды.

другой, на мой взгляд более простой способ:

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

Set launch arguments in Xcode

все аргументы запуска автоматически добавляются в ваш NSUserDefaults.

теперь вы можете получить BOOL, как:

BOOL test = [[NSUserDefaults standardUserDefaults] boolForKey:@"isTest"];

вы можете передать аргументы времени выполнения в приложение в зависимости от схемы здесь...

enter image description here

но я бы спросил, действительно ли это необходимо.

var isRunningTests: Bool {
    return ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] != nil
}

использование

if isRunningTests {
    return "lena.bmp"
}
return "facebook_profile_photo.bmp"

вот способ, который я использовал в Swift 4 / Xcode 9 для наших модульных тестов. Он основан на Джесси.

это не так просто, чтобы предотвратить загрузку раскадровки на всех, но если вы добавите это в начале didFinishedLaunching то это делает его очень ясно для ваших разработчиков, что происходит:

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions:
                 [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    #if DEBUG
    if let _ = NSClassFromString("XCTest") {
        // If we're running tests, don't launch the main storyboard as
        // it's confusing if that is running fetching content whilst the
        // tests are also doing so.
        let viewController = UIViewController()
        let label = UILabel()
        label.text = "Running tests..."
        label.frame = viewController.view.frame
        label.textAlignment = .center
        label.textColor = .white
        viewController.view.addSubview(label)
        self.window!.rootViewController = viewController
        return true
    }
    #endif

(вы, очевидно, не должны делать ничего подобного для тестов пользовательского интерфейса, где вы хотите, чтобы приложение запускалось как обычно!)

комбинированный подход @Jessy и @Michael McGuire

(как принято отвечать, не поможет вам при разработке базы)

Итак, вот код:

#if DEBUG
        if (NSClassFromString(@"XCTest") == nil) {
            // Your code that shouldn't run under tests
        }
#else
        // unconditional Release version
#endif

некоторые из этих подходов не работают с UITests, и если вы в основном тестируете сам код приложения (а не добавляете конкретный код в цель UITest).

Я закончил тем, что установил переменную окружения в методе настройки теста:

XCUIApplication *testApp = [[XCUIApplication alloc] init];

// set launch environment variables
NSDictionary *customEnv = [[NSMutableDictionary alloc] init];
[customEnv setValue:@"YES" forKey:@"APPS_IS_RUNNING_TEST"];
testApp.launchEnvironment = customEnv;
[testApp launch];

обратите внимание, что это безопасно для моего тестирования, так как в настоящее время я не использую никаких других значений launchEnvironment; если вы это сделаете, вы, конечно, захотите сначала скопировать любые существующие значения.

тогда в моем коде приложения, Я ищу эту переменную среды, если/когда я хочу исключить некоторые функции во время теста:

BOOL testing = false;
...
if (! testing) {
    NSDictionary *environment = [[NSProcessInfo processInfo] environment];
    NSString *isRunningTestsValue = environment[@"APPS_IS_RUNNING_TEST"];
    testing = [isRunningTestsValue isEqualToString:@"YES"];
}

Примечание-спасибо за комментарий Ришига, который дал мне эту идею; я просто расширил это до примера.