Як повідомити програму, якщо вона запускає модульні тести в чистому проекті 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.