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 ответов
Вместо того чтобы создавать конфигурацию тестовой сборки, я:
создал
Tests-Prefix.pch
файл:#define TEST 1 #import <SenTestingKit/SenTestingKit.h> #import "CocoaPlant-Prefix.pch"
введите его путь в поле "Заголовок префикса" настроек сборки цели "Тесты".
добавил следующий код в начало файла, который я создал
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(), которое сделал Роберт.
- Изменить текущую схему (Продукт / Схема / Редактировать схему) или Команда +<
- Нажмите на тестовую конфигурацию
- Нажмите на аргументы Снимите флажок "Использовать аргументы действия" Выполнить "и переменные среды".
- Разверните раздел Environment Variable и добавьте переменную TESTING со значением YES
- Добавьте это к своему коду где-нибудь и звоните, когда вам нужно:
+ (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;
}