Xcode:TEST与DEBUG预处理宏

41
当使用Xcode创建一个带有单元测试的新项目时,Xcode会将Test方案(同样适用于Run方案)的构建配置设置为Debug。
我应该区分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

我认为即使我也将其编译为调试版本也没有关系。因此,我真的可以这样做:

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

但是,目前我只打算进行测试。因此,我想区分调试和测试,但我想知道为什么Xcode默认不这样做?苹果认为您不应区分它们吗?


这似乎与Scheme编程语言没有任何关系。(不好的标签) - Ryan Culpepper
@ryanc,是的,我是指Xcode中的schemes。 - ma11hew28
11个回答

38

预处理器宏将不起作用,您需要在运行时检查环境。

Objective-C

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

迅捷

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

(已更新至 Xcode 11)


博客文章链接已损坏。 - Andrea Zonca
2
在Xcode 7.3.1中,此环境变量未被设置。 - Ayush Goel
@AyushGoel - 感谢您的提示。最好的方法是检查环境字典的内容。似乎您可以查找“XCInjectBundleInto”键。我会尽快更新这个内容。 - Robert
快速复制粘贴的Swift版本:`func isRunningTests() -> Bool { let env: [String: String] = NSProcessInfo.processInfo().environment return env["XCInjectBundleInto"] != nil }` - Milan Cermak
我发现将 #if DEBUG 与这种方法结合起来最适合我的需求。这样可以在发布版本中削减相关的单元测试代码,同时仍然允许我们确定调试构建是正常构建还是单元测试。 - Sam Doggett

31

你可以考虑添加一个新的构建配置。

在Xcode 4中,点击左侧导航器上的项目。

在主窗口中,单击您的项目,然后选择“信息”选项卡。

点击“+”按钮以添加新配置(如果愿意,您可以将其称为“test”)。

现在,点击您的目标,进入构建设置选项卡。

搜索“预处理器宏”。

在这里,您可以为新的构建配置添加预处理器宏。

只需双击新的“test”配置,然后添加TESTING = 1即可。

最后,编辑您的构建方案。选择方案的测试选项。应该有一个“构建配置”下拉菜单。选择您的“test”配置。


4
如果您希望在测试代码和常规应用程序中都能使用预处理器宏,那么这绝对是最好的方法。比使用辅助方法的其他解决方案更加清晰。在 Xcode6 中仍然可以完美地工作。 - Lukas Gross
3
如果你正在使用Cocoapods,确保在创建新的构建配置后运行pod install - Snowman

25

我不是创建了一个名为“Test”的构建配置,而是:

  1. 创建了一个Tests-Prefix.pch文件:

    #define TEST 1
    #import <SenTestingKit/SenTestingKit.h>
    #import "CocoaPlant-Prefix.pch"
    
  2. 在测试目标的构建设置中输入了它的路径到前缀头文件字段中。

  3. 在我创建的名为MyAppDefines.h的文件顶部添加了以下代码,然后在MyApp-Prefix.pch中导入:

  4. #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
    

    这使得我可以在任何需要[NSBundle mainBundle]的地方使用BUNDLE,并且在运行测试时也能够正常工作。

    Tests-Prefix.pch中导入SenTestingKit还可以加快SenTestingKit框架的编译速度,并且允许我在所有测试文件的顶部省略#import <SenTestingKit/SenTestingKit.h>


13
在我的单元测试目标预处理宏中添加 #define TEST 1 会在执行的单元测试代码中设置该宏,但不会在被单元测试的应用程序代码中设置。你有什么办法可以实现这一点吗? - shawnwall
3
面临与 @shawnwall 相同的问题:编译应用程序时,编译器上下文中不存在 TEST 变量。只有在编译测试用例本身时才存在。 - yonel
在下面找到了对这些评论问题的答案:https://dev59.com/UWw15IYBdhLWcg3wO5MH#13928458。 - jab
1
在XCTest中的另一个可行之处是[UIApplication sharedApplication]在测试期间返回nil。我想不出还有哪种情况下会返回nil,所以我之前曾将其用作测试标志(如果我错了,请评论!)。 - jab
我发现David的答案要简单得多:https://dev59.com/UWw15IYBdhLWcg3wO5MH#14718914 - Chris Gillum

16

我决定在代码本身中添加对环境变量的检查,而不是使用Robert提出的isRunningTests()建议。

  1. 编辑当前方案(Product / Scheme / Edit Scheme)或 Command + <。
  2. 点击测试配置
  3. 取消选中“使用运行操作的参数和环境变量”复选框
  4. 展开环境变量部分,并添加值为YES的变量TESTING
  5. 将以下内容添加到您的代码中的某个位置,并在需要时调用:
 + (BOOL) isTesting
    {
        NSDictionary* environment = [[NSProcessInfo processInfo] environment];
        return [environment objectForKey:@"TESTING"] != nil;
    }
完成后,屏幕应该看起来像这样。 The screen should look like this 上面的代码将在测试模式或应用程序模式下查找 TESTING 环境变量。这段代码放在应用程序中,而不是单元测试文件中。你可以使用。
#ifdef DEBUG
...
#endif

为了防止代码在生产环境中被执行。


7
如果您创建了一个测试构建配置,然后将您的目标的“其他 Swift 标志”属性设置为“-DTEST”,它将定义一个 TEST 宏,可在您的 Swift 代码中使用。请确保在应用程序目标的构建设置中设置它,以便在应用程序的 Swift 代码中使用它。

Other Swift Flags setting for DEBUG and TEST

然后,使用这个设置,您可以这样测试您的代码:
func testMacro() {
   #if !TEST 
       // skipping over this block of code for unit tests
   #endif
}

5

Robert在SWIFT 3.0中的回答:

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

3

我测试了很长时间,得出了这样的结果:

不仅需要将预处理器宏添加到您的单元测试目标中(您可以使用许多方法只为单元测试使用变量,并遵循@MattDiPasquale的方法),

而且您还必须在测试目标中添加条件编译文件。我们应该重新编译此文件,因为您为此文件设置了一个新的预处理器宏,但是当时该文件已内置于应用程序目标中,而您的预处理器宏未设置。

希望这可以帮助你。


换句话说,对于主目标中的任何文件,在测试期间想要定义TEST预处理器宏的情况下,请将该文件添加到测试目标下的“Build Phases”中的“Compile Sources”列表中。 - jab

2

已更新至Xcode10:

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

0

基于Kev的答案,我修改了一下,在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;
}

0
查看环境变量以查看单元测试是否正在运行。与罗伯特的答案类似,但出于性能考虑,我只检查一次。
+ (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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接