Як повідомити програму, якщо вона запускає модульні тести в чистому проекті Swift?


86

Одна неприємна річ під час запуску тестів у Xcode 6.1 - це те, що весь додаток повинен запустити та запустити свій раскадровку та контролер кореневого перегляду. У моєму додатку це запускає деякі виклики сервера, які отримують дані API. Однак я не хочу, щоб програма робила це під час запуску тестів.

Поки макроси препроцесора не працюють, що найкраще для мого проекту усвідомлювати, що його було запущено під час запуску тестів, а не звичайного запуску? Я запускаю їх нормально з command+ Uі на боті.

Псевдокод:

// Appdelegate.swift
if runningTests() {
   return
} else {
   // do ordinary api calls
}

"весь додаток повинен запустити і запустити свою розкадрування та контролер кореневого перегляду" це правильно? Я його не тестував, але мені це не здається правильним. Хм ...
— Фогмайстер

Так, додаток завершив запуск запущений, а також viewdidload для кореневого контролера перегляду
— bogen

Ах, просто перевірено. Не думав, що це так, хаха. Що саме з цього призводить до проблем у ваших тестах? Може, є інший спосіб обійти це?
— Fogmeister

Мені потрібно просто повідомити програму про свій запуск, маючи на увазі тести, щоб прапор, такий як старі макроси препроцесора, працював, але вони не підтримуються швидко.
— bogen

Так, але навіщо тобі "потрібно" це робити? Що саме змушує думати, що потрібно це робити?
— Fogmeister

Відповіді:


44

Замість того, щоб перевіряти, чи працюють тести, щоб уникнути побічних ефектів, ви можете запускати тести без самої програми-хоста. Перейдіть до Налаштування проекту -> виберіть ціль тесту -> Загальне -> Тестування -> Хост-програма -> виберіть «Немає». Тільки не забудьте включити всі файли, необхідні для запуску тестів, а також бібліотеки, які зазвичай входять у ціль програми Host.

введіть тут опис зображення


1
Як включити мостові заголовки для цілі після видалення хост-програми?
— Бхаргав,

1
Пробував цей, він зламав свої тести , поки я не отримав таку відповідь , який пропонує зробити як раз навпаки: stackoverflow.com/a/30939244/1602270
— eagle.dan.1349

якщо я це зроблю, я можу протестувати cloudKit (наприклад), тому рішення для мене - виявити в applicationDidFinishLaunching, якщо я тестую, а якщо ТАК, то повернутись, не виділяючи основні класи програми.
— user1105951

86

Відповідь Ельвінда непогана, якщо ви хочете мати те, що раніше називалося чистими "Логічними тестами". Якщо ви все-таки хочете запустити вміщувальну програму, яка все ж виконується умовно чи не виконує код, залежно від того, запущені тести, ви можете використати наступне, щоб виявити, чи був введений тестовий пакет:

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
}

3
Не працює в останньому Xcode - назва змінної середовища, схоже, змінилася
— amleszk

7
Не впевнений, коли саме це перестало працювати, але з Xcode 7.3 я зараз використовую XCTestConfigurationFilePathключ середовища замість XCInjectBundle.
— ospr

Дякую @ospr, я відредагував відповідь для роботи з Xcode 7.3
— Michael McGuire

У @tkuichooseyou po ProcessInfo.processInfo.environmentнемає ключа XCTestConfigurationFilePath. Ви можете поділитися своїм кодом? Це перевірено після цілі UITest
— Тал-Ціон

@TalZion вибачте, не зрозумів, що ви призначені для цілі UITest. Ви пробували NSClassFromString("XCTest")метод нижче?
— tkuichooseyou 07

44

Я використовую це в додатку: didFinishLaunchingWithOptions:

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

3
Це стосується лише модульних тестів, а не нових тестів інтерфейсу користувача Xcode 7.
— Джессі,

34

Інший, на мій погляд, простіший спосіб:

Ви редагуєте свою схему, щоб передати логічне значення як аргумент запуску додатку. Подобається це:

Встановіть аргументи запуску в Xcode

Усі аргументи запуску автоматично додаються до вашого NSUserDefaults.

Тепер ви можете отримати BOOL, як:

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

2
Здається, це найчистіший спосіб, який я знайшов досі. Нам не потрібно додавати всі файли додатків до тестової цілі, і нам не потрібно покладатися на якесь дивне рішення, яке перевіряє на "XCTestConfigurationFilePath"чи NSClassFromString("XCTest"). Я впровадив це рішення в Swift із глобальною функцієюfunc isRunningTests() -> Bool { return UserDefaults.standard.bool(forKey: "isRunningTests") }
— Кевін Гірш,

21

Я вважаю, що цілком законно хотіти знати, працюєш ти в тесті чи ні. Існує безліч причин, чому це може бути корисним. Наприклад, під час запуску тестів я повертаюся раніше від методів application-did / will-finish-launch у App Delegate, роблячи тести швидшими для коду, який не є загальним для мого модульного тесту. Проте я не можу пройти чистий "логічний" тест з цілої низки інших причин.

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

А саме, я більше не бачу XCInjectBundle під час запуску тесту в тестовій мішені для мого фреймворка. Тобто я працюю всередині тестової цілі, яка тестує фреймворк.

Отже, використовуючи підхід, який пропонує @Fogmeister, кожна з моїх тестових схем тепер встановлює змінну середовища, на яку я можу перевірити.

введіть тут опис зображення

Потім, ось якийсь код, який я маю в класі, 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 (тобто, не вибравши одну зі своїх тестових схем), ви не отримаєте цієї змінної середовища.


5
замість того, щоб додавати його в "Виконати", чи не було б корисніше, якщо ми додамо його в "Тест" для всіх схем? Таким чином, isRunningTests працюватиме у всіх схемах.
— Vishal Singh,

@VishalSingh Так, я вважаю, що це чистіше. Ви спробували такий підхід? Повідомте нас, якщо це спрацювало так само добре для вас.
— idStar

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

Використання

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

На відміну від деяких інших відповідей, ця працює. Дякую.
— n13

8

Комбінований підхід @Jessy та @Michael McGuire

(Відповідно до прийнятої відповіді вам не допоможе при розробці фреймворку)

Отже, ось код:

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

1
Це набагато приємніше! Коротше і сумісні з фреймворком!
— blackjacx

Працював над пакетом Swift, і це спрацювало для мене
— Д. Грег,

4

Ось спосіб, який я використовував у 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

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


3

Ви можете передавати аргументи середовища виконання в додаток залежно від схеми тут ...

введіть тут опис зображення

Але я б сумнівався, чи це насправді потрібно.


3

Це швидкий спосіб зробити це.

extension Thread {
  var isRunningXCTest: Bool {
    for key in self.threadDictionary.allKeys {
      guard let keyAsString = key as? String else {
        continue
      }

      if keyAsString.split(separator: ".").contains("xctest") {
        return true
      }
    }
    return false
  }
}

І ось як ви ним користуєтесь:

if Thread.current.isRunningXCTest {
  // test code goes here
} else {
  // other code goes here
}

Ось повна стаття: https://medium.com/@theinkedengineer/check-if-app-is-running-unit-tests-the-swift-way-b51fbfd07989


Цикл for можна Swiftilized наступним чином:return threadDictionary.allKeys.anyMatch { ($0 as? String)?.split(separator: ".").contains("xctest") == true }
— Роджер Оба

2

Деякі з цих підходів не працюють з UITests, і якщо ви в основному тестуєте за допомогою самого коду програми (а не додаєте конкретний код у ціль UITest).

У підсумку я встановив змінну середовища в методі setUp тесту:

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"];
}

Примітка - дякую за коментар RishiG, який дав мені цю ідею; Я просто розширив це на приклад.


2

Метод, який я використовував, перестав працювати в Xcode 12 beta 1. Після випробування всіх відповідей на це питання на основі побудови, мене надихнула відповідь @ ODB. Ось версія Swift досить простого рішення, яке працює як для реальних пристроїв, так і для симуляторів. Це також повинно бути досить "вивільненням".

Вставити в тестове налаштування:

let app = XCUIApplication()
app.launchEnvironment.updateValue("YES", forKey: "UITesting")
app.launch()

Вставити в додаток:

let isTesting: Bool = (ProcessInfo.processInfo.environment["UITesting"] == "YES")

Щоб використовувати його:

    if isTesting {
        // Only if testing
    } else {
        // Only if not testing
    }

0

Працював у мене:

Завдання-C

[[NSProcessInfo processInfo].environment[@"DYLD_INSERT_LIBRARIES"] containsString:@"libXCTTargetBootstrapInject"]

Стрімкий: ProcessInfo.processInfo.environment["DYLD_INSERT_LIBRARIES"]?.contains("libXCTTargetBootstrapInject") ?? false


0

Спочатку додайте змінну для тестування:

введіть тут опис зображення

і використовуйте це у своєму коді:

 if ProcessInfo.processInfo.environment["IS_UNIT_TESTING"] == "1" {
                 // Code only executes when tests are running
 } 

0

Очевидно, у Xcode12 нам потрібно шукати за ключем середовища, XCTestBundlePathа не XCTestConfigurationFilePathякщо ви використовуєте новийXCTestPlan

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.