samurai-native 学习笔记--samurai中的单元测试

来源:互联网 发布:淘宝工作总结范文 编辑:程序博客网 时间:2024/05/21 14:49

囧么说好呢,大神就是任性,自己写了个单元测试类,我们来看看吧

使用

// ----------------------------------// 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

实现

// TEST_CASE宏展开后是一个类,前面一个参数是模块名字#define TEST_CASE( __module, __name ) \        @interface __TestCase__##__module##_##__name : SamuraiTestCase \        @end \        @implementation __TestCase__##__module##_##__name// TEST_CASE_END 其实就是 @end#undef  TEST_CASE_END#define TEST_CASE_END \        @end
// DESCRIBE 展开后是被测试的方法,方法名字是runTest_xxx#undef  DESCRIBE#define DESCRIBE( ... ) \        - (void) macro_concat( runTest_, __LINE__ )
// EXPECTED 展开后如下,如果检测不过,抛出异常#define EXPECTED( ... ) \        if ( !(__VA_ARGS__) ) \        { \            @throw [SamuraiTestFailure expr:#__VA_ARGS__ file:__FILE__ line:__LINE__]; \        }
// REPEAT 和 TIMES 展开后是重复#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__ )

所有的测试类都是继承自SamuraiTestCase,可是我们看了下SamuraiTestCase的定义结果是一个空的类,定义这个类的作用是为了用runtime找出所有这个类的子类.

通过源码我们可以看出SamuraiUnitTest的实现原理是执行了所有SamuraiTestCase子类的runTest_开头的方法,并且用line排序,实现了特定的初始化方法before和结束方法after,如果验证不过,则抛出一个异常.

下面是SamuraiUnitTest的核心方法

- (void)run{    fprintf( stderr, "  =============================================================\n" );    fprintf( stderr, "   Unit testing ...\n" );    fprintf( stderr, "  -------------------------------------------------------------\n" );    // 获取所有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 stringByReplacingOccurrencesOfString:@"__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];                // 获取所有runTest_开头的方法,runTest_开头的方法看前面的宏定义可以自导后面一个参数是行号                // Samurai在methodsWithPrefix:untilClass:方法里又进行了排序                // 这就是前面的DESCRIBE(before)(after)的实现原理                NSArray * selectorNames = [classType methodsWithPrefix:@"runTest_" untilClass:[SamuraiTestCase class]];                if ( selectorNames && [selectorNames count] )                {                    for ( NSString * selectorName in selectorNames )                    {                        SEL selector = NSSelectorFromString( selectorName );                        // 执行这个方法                        if ( selector && [testCase respondsToSelector:selector] )                        {                            NSMethodSignature * signature = [testCase methodSignatureForSelector:selector];                            NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:signature];                            [invocation setTarget:testCase];                            [invocation setSelector:selector];                            [invocation invoke];                        }                    }                }            }            @catch ( NSException * e )            {                if ( [e isKindOfClass:[SamuraiTestFailure 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 = CACurrentMediaTime();        // 记录时间        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) * 100.0f;    fprintf( stderr, "  -------------------------------------------------------------\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;}
1 0
原创粉丝点击