Xcode: TEST против макросов препроцессора DEBUG

При создании нового проекта с модульными тестами XCode устанавливает конфигурацию сборки на Debug для схемы Test (то же самое для схемы Run).

Должен ли я различать схемы Run (Command-R) и Test (Command-U)?

То есть, следует ли мне создать новую конфигурацию сборки с именем Test, добавить к ней макрос препроцессора TEST=1 и использовать вместо этого в качестве конфигурации сборки для схемы Test? Или я должен оставить Run & Test для Debug?

Я родом из Ruby/Rails, где у вас обычно есть среда тестирования, разработки и производства. Мне кажется, что Debug - это как разработка, а Release - как производство, но нам не хватает теста, поэтому я думаю, что имеет смысл добавить Test.

Комментарии? Мнения? Предложения?

Я специально спрашиваю об этом, потому что я хочу скомпилировать что-то для Test с:

#ifdef TEST
// Do something when I test.
#endif

Я не думаю, что это имеет значение, если я также скомпилирую это для Debug. Итак, я действительно мог бы просто сделать:

#ifdef DEBUG
// Do something when I run or test.
#endif

Но сейчас я действительно собираюсь сделать это только для тестов. Итак, вот почему я думаю, что я должен различать отладку и тестирование, но мне интересно, почему XCode не делает это для вас по умолчанию? Apple считает, что вы не должны различать их?

11 ответов

Решение

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

  1. создал Tests-Prefix.pch файл:

    #define TEST 1
    #import <SenTestingKit/SenTestingKit.h>
    #import "CocoaPlant-Prefix.pch"
    
  2. введите его путь в поле "Заголовок префикса" настроек сборки цели "Тесты".

  3. добавил следующий код в начало файла, который я создал MyAppDefines.hимпортируется в MyApp-Prefix.pch:

    #ifdef TEST
    #define TEST_CLASS NSClassFromString(@"AppDelegateTests") // any test class
    #define BUNDLE [NSBundle bundleForClass:TEST_CLASS]
    #define APP_NAME @"Tests"
    #else
    #define BUNDLE [NSBundle mainBundle]
    #define APP_NAME [[BUNDLE infoDictionary] objectForKey:(NSString *)kCFBundleNameKey]
    #endif
    

Это позволяет мне использовать BUNDLE где бы я ни имел ввиду [NSBundle mainBundle] а также заставить его работать, когда я запускаю тесты.

Импортирование SenTestingKit в Tests-Prefix.pch также ускоряет компиляцию инфраструктуры SenTestingKit и позволяет мне опустить #import <SenTestingKit/SenTestingKit.h> сверху всех тестовых файлов.

Макросы препроцессора не будут работать, вам нужно проверять среду во время выполнения.

static BOOL isRunningTests(void)
{
    NSDictionary* environment = [[NSProcessInfo processInfo] environment];
    return (environment[@"XCInjectBundleInto"] != nil);
}

(Обновлено для Xcode 7.3)

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

В xcode 4, нажмите на ваш проект в левой части навигатора.

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

Нажмите кнопку "+", чтобы добавить новую конфигурацию (вы можете назвать свою "тест", если хотите ").

Теперь нажмите на свою цель и перейдите на вкладку настроек сборки.

Искать "препроцессорные макросы"

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

Просто дважды щелкните по вашей новой "тестовой" конфигурации и добавьте TESTING=1.

Наконец, отредактируйте схему сборки. Выберите параметры теста для вашей схемы. Должно быть выпадающее меню "Конфигурация сборки". Выберите свою "тестовую" конфигурацию.

Я решил добавить проверку на переменную среды в самом коде вместо того, чтобы использовать предложение isRunningTests(), которое сделал Роберт.

  1. Изменить текущую схему (Продукт / Схема / Редактировать схему) или Команда +<
  2. Нажмите на тестовую конфигурацию
  3. Нажмите на аргументы Снимите флажок "Использовать аргументы действия" Выполнить "и переменные среды".
  4. Разверните раздел Environment Variable и добавьте переменную TESTING со значением YES
  5. Добавьте это к своему коду где-нибудь и звоните, когда вам нужно:
 + (BOOL) isTesting
    {
        NSDictionary* environment = [[NSProcessInfo processInfo] environment];
        return [environment objectForKey:@"TESTING"] != nil;
    }

Экран должен выглядеть следующим образом.

Экран должен выглядеть так

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

#ifdef DEBUG
...
#endif

Для предотвращения выполнения кода в производстве.

Если вы создадите конфигурацию тестовой сборки и затем установите для свойства "Другие флаги Swift" вашей цели значение "-DTEST", это определит макрос TEST, который будет работать в вашем коде swift. Убедитесь, что вы установили его в настройках сборки целевого приложения, чтобы вы могли использовать его в коде Swift вашего приложения.

Настройка других быстрых флагов для отладки и тестирования

Затем с помощью этого набора вы можете проверить свой код следующим образом:

func testMacro() {
   #if !TEST 
       // skipping over this block of code for unit tests
   #endif
}

Ответ Роберта в SWIFT 3.0:

func isRunningTests() -> Bool {
    let environment = ProcessInfo().environment
    return (environment["XCInjectBundleInto"] != nil);
}

Я тестировал так долго, нашел результат:

Вы не только добавляете макрос препроцессора в свою цель модульного теста (у вас может быть много методов, использующих переменные только для модульного тестирования, и следуйте методам @MattDiPasquale),

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

Надеюсь, это поможет вам.

Обновлено для Xcode10:

static let isRunningUnitTests: Bool = {
    let environment = ProcessInfo().environment
    return (environment["XCTestConfigurationFilePath"] != nil)
}()

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

+ (BOOL)isRunningTests {
   static BOOL runningTests;
   static dispatch_once_t onceToken;

   // Only check once
   dispatch_once(&onceToken, ^{
      NSDictionary* environment = [[NSProcessInfo processInfo] environment];
      NSString* injectBundle = environment[@"XCInjectBundle"];
      NSString* pathExtension = [injectBundle pathExtension];
      runningTests = ([pathExtension isEqualToString:@"octest"] ||
                      [pathExtension isEqualToString:@"xctest"]);
   });
   return runningTests;
}

На iOS [UIApplication sharedApplication] вернусь nil когда юнит тест запущен.

Модифицированная версия ответа Кева, которая работает для меня на Xcode 8.3.2

+(BOOL)isUnitTest {

    static BOOL runningTests;
    static dispatch_once_t onceToken;

    // Only check once
    dispatch_once(&onceToken, ^{
        NSDictionary* environment = [[NSProcessInfo processInfo] environment];
        if (environment[@"XCTestConfigurationFilePath"] != nil && ((NSString *)environment[@"XCTestConfigurationFilePath"]).length > 0) {
            runningTests = true;
        } else {
            runningTests = false;
        }
    });
    return runningTests;
}
Другие вопросы по тегам