Reputation: 5896
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
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 #import
ed 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
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