Samurai Framework learning notes -- unit test in samurai, samurai -- samurai
Okay? Let's take a look. I wrote a unit test class myself.
Use
// ----------------------------------// Unit test// ----------------------------------#pragma mark -TEST_CASE( Core, NSDictionary_Extension ){ NSDictionary * _testDict;}DESCRIBE( before ){ _testDict = @{ @"k1": @"v1", @"k2": @"v2", @"k3": @3, @"k4": @{ @"a": @4 } };}DESCRIBE( objectAtPath ){ id value1 = [_testDict objectForOneOfKeys:@[@"k1", @"k2"]]; id value2 = [_testDict objectForOneOfKeys:@[@"k2"]]; EXPECTED( [value1 isEqualToString:@"v1"] ); EXPECTED( [value2 isEqualToString:@"v2"] ); id value3 = [_testDict numberForOneOfKeys:@[@"k3"]]; EXPECTED( [value3 isEqualToNumber:@3] ); id value4 = [_testDict stringForOneOfKeys:@[@"k1"]]; EXPECTED( [value4 isEqualToString:@"v1"] ); id obj1 = [_testDict objectAtPath:@"k4.a"]; EXPECTED( [obj1 isEqualToNumber:@4] ); id obj2 = [_testDict objectAtPath:@"k4.b"]; EXPECTED( nil == obj2 ); obj2 = [_testDict objectAtPath:@"k4.b" otherwise:@"b"]; EXPECTED( obj2 && [obj2 isEqualToString:@"b"] ); id obj3 = [_testDict objectAtPath:@"k4"]; EXPECTED( obj3 && [obj3 isKindOfClass:[NSDictionary class]] );}DESCRIBE( after ){ _testDict = nil;}TEST_CASE_END
Implementation
// TEST_CASE macro is a class. The previous parameter is the module name # define TEST_CASE (_ module, _ name) \ @ interface _ TestCase __#__module #####__name: samuraiTestCase \ @ end \ @ implementation _ TestCase __#__module ######__name // TEST_CASE_END is actually @ end # undef TEST_CASE_END # define TEST_CASE_END \ @ end
// DESCRIBE is the tested method after it is expanded. The method name is runTest_xxx # undef DESCRIBE # define DESCRIBE (...) \-(void) macro_concat (runTest _, _ LINE __)
// After EXPECTED is expanded, if the detection fails, an exception is thrown. # define EXPECTED (...) \ if (! (_ VA_ARGS _) \{\@ throw [SamuraiTestFailure expr :#__ VA_ARGS _ file :__ FILE _ line :__ LINE _]; \}
// REPEAT and TIMES are repeated. # undef REPEAT # define REPEAT (_ n) \ for (int _ I _ ##__line _ = 0; _ I _ ##__line _ <_ n; ++ _ I _ ##__line _) # undef TIMES # define TIMES (_ n) \/* [[SamuraiUnitTest sharedInstance] writeLog: @ "Loop % d times @ % @ (# % d)", _ n, [@ (_ FILE __) lastPathComponent], _ LINE _]; */\ for (int _ I _ ##__line _ = 0; _ I _ ##__line _ <_ n; ++ _ I _ ##__line __)
All the test classes are inherited from SamuraiTestCase, but we have read that the result of SamuraiTestCase definition is an empty class. The purpose of defining this class is to use runtime to find all the subclasses of this class.
Through the source code, we can see that the implementation principle of SamuraiUnitTest is to execute the Methods Starting with runTest _ Of All SamuraiTestCase subclasses, and sort them by line to implement the specific initialization method before and end method after, if the verification fails, an exception is thrown.
The following describes the core SamuraiUnitTest method.
-(Void) run {fprintf (stderr, "============================================== =======================================\ n "); fprintf (stderr, "Unit testing... \ n "); fprintf (stderr," classes \ n "); // obtain all the subClasses of SamuraiTestCase NSArray * classes = [SamuraiTestCase subClasses]; LogLevel filter = [SamuraiLogger sharedInstance]. filter; [SamuraiLogger sharedInstance]. filter = LogLevel_Warn; // [SamuraiLogger sharedInstance]. filter = LogLevel_All; CFTimeInterval beginTime = CACurrentMediaTime (); for (NSString * className in classes) {Class classType = NSClassFromString (className); if (nil = classType) continue; NSString * testCaseName; testCaseName = [classType description]; testCaseName = [testCaseName scheme: @ "_ TestCase _" withString: @ "TEST_CASE ("]; testCaseName = [testCaseName stringByAppendingString: @ ")"]; NSString * formattedName = [testCaseName stringByPaddingToLength: 48 withString: @ "" startingAtIndex: 0]; // [[SamuraiLogger sharedInstance] disable]; fprintf (stderr, "% s", [formattedName UTF8String]); CFTimeInterval time1 = CACurrentMediaTime (); BOOL testCasePassed = YES; // @ autoreleasepool {@ try {SamuraiTestCase * testCase = [[classType alloc] init]; // obtain Methods Starting with runTest, the method starting with runTest _ indicates that the previous macro definition can export the following parameter: line number // Samurai in methodsWithPrefix: untilClass: The method is sorted again // This is the previous DESCRIBE (before) (after) implementation principle NSArray * selectorNames = [classType methodsWithPrefix: @ "runTest _" untilClass: [SamuraiTestCase class]; if (selectorNames & [selectorNames count]) {for (NSString * selectorName in selectorNames) {SEL selector = NSSelectorFromString (selectorName); // execute this method if (selector & [testCase respondsToSelector: selector]) {NSMethodSignature * signature = [testCase sequence: selector]; NSInvocation * invocation = [NSInvocation sequence: signature]; [invocation setTarget: testCase]; [invocation setSelector: selector]; [invocation invoke] ;}}}@ catch (NSException * e) {if ([e isKindOfClass: [classes class]) {SamuraiTestFailure * failure = (SamuraiTestFailure *) e; [self writeLog: @ "\ n" "% @ (# % lu) \ n" "\ n" "{\ n" "EXPECTED (% @); \ n "" ^ \ n "" Assertion failed \ n ""} \ n "" \ n ", failure. file, failure. line, failure. expr];} else {[self writeLog: @ "\ nUnknown exception '% @'", e. reason]; [self writeLog: @ "% @", e. callStackSymbols] ;}testcasepassed = NO ;}@ finally {}; CFTimeInterval time2 = cacur=mediatime (); // record time CFTimeInterval time = time2-time1; // [[SamuraiLogger sharedInstance] enable]; if (testCasePassed) {_ succeedCount + = 1; fprintf (stderr, "[OK] %. 003fs \ n ", time);} else {_ failedCount + = 1; fprintf (stderr," [FAIL] %. 003fs \ n ", time);} [self flushLog];} CFTimeInterval endTime = CACurrentMediaTime (); CFTimeInterval totalTime = endTime-beginTime; float passRate = (_ succeedCount * 1.0f) /(_ succeedCount + _ failedCount) * 1.0f) * 100366f; fprintf (stderr, "Limit \ n"); fprintf (stderr, "Total % lu cases [%. 0f %] %. 003fs \ n ", (unsigned long) [classes count], passRate, totalTime); fprintf (stderr, "============================================== =======================================\ n "); fprintf (stderr, "\ n"); [SamuraiLogger sharedInstance]. filter = filter ;}