steventnorris
steventnorris

Reputation: 5896

C Preprocessor to Determine if in Unit Test

I am attempting to use c macros to determine if I am in unit testing, but I am fairly new to the C preprocessor. Essentially, I am attempting to use the below.

@implementation thisThing

+(void)thisMethod{
#if TESTING == 1
NSLog(@"TESTING");
#else
NSLog(@"NOT TESTING");
#endif
}

@end

Then in the header of the test file

#define TESTING = 1
@interface tester : XCTestCase

@end

@implementation tester

-(void)testTheThing{
XCTAssertEqual(1,1);
}

However, with this method I still only get the "NOT TESTING" log message. Any help would be appreciated.

@end

Upvotes: 0

Views: 1274

Answers (2)

Alex Celeste
Alex Celeste

Reputation: 13370

As an supplementary answer to actually explain the preprocessor behaviour in the question:

There are two problems with the code as given that will lead to unwanted behaviour. First, as Brian points out, because you neither #include nor #import the header file with the definition, the constant is not visible when the .m file is compiled. This is because the preprocessor operates on a completely different level from the Objective-C language proper; #directives form a compile-time program that executes "down" the file being passed through the preprocessor, and that ends when the end of the file is reached. Thus definitions stop existing at all once the end of the file is reached; the only way to make them visible in an implementation file is to stitch the two files into one "translation unit" with #include or #import.

Because the definition in the header is having no effect on the .m file, the preprocessor is proceeding without TESTING having been declared at all. You might expect this to cause a compiler error, but it doesn't because the preprocessor language handles undefined names used in conditional expressions ("missing variables", if you like) by silently assuming they should expand to 0. It will never warn you about this behaviour (this one reason to consider using #ifdef instead of #if, too). So the actual comparison you're running in the .m file expands to #if 0 == 1, with now-predictable results.

The second problem is that your definition syntax is wrong. #define doesn't use = - everything that appears after the constant name is part of the definition (this is necessary as macros are also often used to insert symbols). To define TESTING as 1, you need to write:

#define TESTING 1

No = (and as you already know, no ; or anything else either). Whereas the definition in the question will cause TESTING to literally expand to the sequence = 1, which isn't going to make sense in many contexts. If you had #imported the header file, you would have received an error on the #if line, for the obvious reason that = 1 is not a number, and cannot meaningfully be compared to 0.

Upvotes: 1

Brian Nickel
Brian Nickel

Reputation: 27560

Defines are preprocessor macros, meaning they only live in the file(s) being compiled when they are being compiled. This means that for TESTING == 1 to be true you would have had to have imported tester.m into thisThing.m. You could definite -dTESTING=1 as a compiler instruction in Xcode, but this would make it always on for that build configuration, even when not testing.

More in line with what you were attempting is creating a variable in your main project, then in your test project, reference it as an extern variable and set it to YES:

Main implementation:

BOOL TESTING = NO;

@implementation thisThing

+(void)thisMethod{
    if (TESTING) {
        NSLog(@"TESTING");
    } else {
        NSLog(@"NOT TESTING");
    }
}

@end

Test file:

extern BOOL TESTING;

@implementation MyTest

+ (void)initialize
{
    TESTING = YES;
}

Probably easier is simply checking whether or not your app was launched with an XCTest bundle:

BOOL isTesting = [[[NSProcessInfo processInfo] arguments] containsObject:@"-XCTest"];

Generally though, it is best if your project does not know it is being tested. Otherwise you run the risk of not testing actual behavior and pollute your main project with testing code.

Upvotes: 2

Related Questions