单元测试 Tests 的简介及使用

![Upload Snip20161222_3.png failed. Please try again.]](http://upload-images.jianshu.io/upload_images/2932245-4223a9df12f72723.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

单元测试的代码都要以 test 开头,这样才会有左侧的菱形块,鼠标点击就能运行单元测试代码**

  • 1.单元测试是以代码测试代码 ;

  • 2.红灯,绿灯迭代开发 ;

  • 3.在日常开发中,数据大部分来自于网络,很难出现所有的边界数据(比如:年龄非法,过大或者为负时) ,此时如果没有测试所有的条件就上架,那么在运行时会造成闪退!用户体验不好!

  • 4.单元测试里可以自主建立"测试用例",以用于专门检查边界条件!

  • 5.在单元测试中打印 NSLog函数,发现交互并不好~显示的东西太多了,那是因为单元测试中并不是以 NSLog 函数为测试方法的! NSLog 函数是程序猿用眼睛看自己判断的方法,单元测试用的是'断言 --> XCTAssert '来测试,"提前预判"条件必须满足!

  • 6.为什么有些公司不进行单元测试: 代码覆盖度不好!有些公司认为只要是函数就要测试,这样需要测试的代码非常非常多!很麻烦!逻辑不断重构之后好多写好的测试代码来不及调整!

提示:
  • 1.不是所有的代码都需要进行单元测试! 面向对象的方法有一个原则:开闭原则 " 对外开发,对内封闭"
    • <1>类里的私有方法不用进行测试!
    • <2>.h文件(接口文件)中的代码的实现需要测试!
    • <3>所有和 UI 有关的代码都不需要进行测试,同时也不好测试!因为 UI 测试需要用户交互,我们用代码检测的是一些极端的边界条件的测试,并不包含用户的鬼神操作!
  • 2.MVVM 的设计模式中:它把小的业务逻辑代码封装出来放到 ViewModel 中,把它们了可以测试的代码,让程序的健壮性得以实现!
  • 3.单元测试的代码的覆盖度一般要求在70%以内!这已经算是非常优秀的程序了!基本能够保证超过你虚的健壮性!
    • <1>我们可以打开 AFNetworking, 会发现 AFN 的图片下册有一行导航栏模块,第二个模块中写着 Codecov--->这就是 代码的单元覆盖度!
    • <2>郭曜源的 YYModel 中的代码的单元覆盖度达到了惊人的99%,足以见得他本人开源这份代码下了多少时间和精力!

注意一些主要方法:

  • 1.XCTAssert 断言 ;
  • 2.XCTAssert(age == person.age , @"年龄不正确") 他的意思是 : 如果 age == person.age 这个条件不满足,那就显示"年龄不正确":
  • 3.在性能测试中老的方法是NSTimeInterval start = CACurrentMediaTime() 配合 NSLog(@"* --> %f <-- *" , CACurrentMediaTime() - start) ;来达到检测代码耗时性能的目的!
    在单元测试里用的是新方法!!!一定注意看我的 testPerformanceExample 部分的代码~这里系统会自动执行10遍我的 for 循环,并输出每一次的时间和平均时间(详见代码和下图)
单元测试 Tests 的简介及使用_第1张图片
Snip20161222_3.png
  • 4.异步加载任务时候的单元测试用到了新方法:expectation-->预期 ;
    • XCTestExpectation *expectation = [self expectationWithDescription:@"异步加载 Person"] ; //创建一个expectation 预期 ;
    • [expectation fulfill] ;//标注预期完成 ;
    • [self waitForExpectationsWithTimeout: 1.0f handler:^(NSError * _Nullable error) { }] ;//等待预期达成://我认为的认为 1.0f 秒之内预期达成我都认为是合理的:

PersonTests.m 文件

#import 
#import "Person.h"
@interface PersonTests : XCTestCase

@end

@implementation PersonTests

/**
 一次单元测试开始前的准备工作,可以设置全局变量:
 类似于:[[self alloc] init] ;
 */
#pragma mark - setUp 设置
- (void)setUp {
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
    //  请在此处键入设置代码.这个方法将在本类中的每一个测试方法(以 test 开头的方法)之前被调用.
}

/**
 一次单元测试结束前的销毁工作:
 类似于 dealloc ;
 */
#pragma mark - tearDown 销毁
- (void)tearDown {
    [super tearDown];
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    // 请在此处键入销毁代码.这个方法将在本类中的每一个测试方法以 test 开头的方法)之后被调用.
}

//单元测试的代码都要以 test 开头,这样才会有左侧的菱形块:
/**
    1.单元测试是以代码测试代码 ;
    2.红灯,绿灯迭代开发 ;
    3.在日常开发中,数据大部分来自于网络,很难出现所有的边界数据(比如:年龄非法,过大或者为负时) ,此时如果没有测试所有的条件就上架,那么在运行时会造成闪退!用户体验不好!
    4.单元测试里可以自主建立"测试用例",以用于专门检查边界条件!
    5.在单元测试中打印 NSLog函数,发现交互并不好~显示的东西太多了,那是因为单元测试中并不是以 NSLog 函数为测试方法的! NSLog 函数是程序猿用眼睛看自己判断的方法,单元测试用的是'断言 --> XCTAssert '来测试,"提前预判"条件必须满足!
 
    6.为什么有些公司不进行`单元测试`:
        代码覆盖度不好!有些公司认为只要是函数就要测试,这样需要测试的代码非常非常多!很麻烦!逻辑不断重构之后好多写好的测试代码来不及调整!
 提示:
    1.不是所有的代码都需要进行单元测试! 面向对象的方法有一个原则:`开闭原则` --> 对外开发,对内封闭!
        <1>类里的私有方法不用进行测试!
        <2>.h文件(接口文件)中的代码的实现需要测试!
        <3>所有和 UI 有关的代码都不需要进行测试,同时也不好测试!因为 UI 测试需要用户交互,我们用代码检测的是一些极端的边界条件的测试,并不包含用户的`鬼神操作`!
    2.MVVM 的设计模式中:它把小的业务逻辑代码封装出来放到 ViewModel 中,把它们了可以测试的代码,让程序的健壮性得以实现!
    3.单元测试的代码的覆盖度一般要求在70%以内!这已经算是非常优秀的程序了!基本能够保证超过你虚的健壮性!
        <1>我们可以打开 AFNetworking, 会发现 AFN 的图片下侧有一行导航栏模块,第二个模块中写着 `Codecov`--->这就是 `代码的单元覆盖度`!
        <2>郭曜源的 YYModel 中的代码的单元覆盖度达到了惊人的99%,足以见得他本人开源这份代码下了多少时间和精力!
 
 */

#pragma mark - 测试 Person 模型
- (void)testNewPerson {
    [self checkPersonWithDictionary:@{ @"name":@"zhangsan" , @"age":@26 }] ;
    [self checkPersonWithDictionary:@{ @"name":@"zhangsan" }] ;
    //空字典:
    [self checkPersonWithDictionary:@{}] ;
    [self checkPersonWithDictionary:@{ @"name":@"zhangsan" , @"age":@26 , @"title":@"leader" }] ;
    //非法年龄数据:
    [self checkPersonWithDictionary:@{ @"name":@"zhangsan" , @"age":@500 , @"title":@"leader" }] ;
    [self checkPersonWithDictionary:@{ @"name":@"zhangsan" , @"age":@-1 , @"title":@"leader" }] ;
    //至此,单元测试的工厂方法测试基本完成!
    
}

#pragma mark - 检测 Person 信息方法
- (void)checkPersonWithDictionary:(NSDictionary *)dictionary {
    Person *person = [Person personWithDictionary:dictionary] ;
    NSLog(@"%@" , person) ;
    //获取字典信息:
    NSString *name = dictionary[@"name"] ;
    NSInteger age = [dictionary[@"age"] integerValue] ;
    //XCTAssert:断言 ;
    //1.检查名称:
    //如果 [name isEqualToString:person.name] 这个条件不满足那就显示"姓名不一致":
    XCTAssert([name isEqualToString:person.name] || person.name == nil , @"姓名不一致") ;
    //2.检查年龄:
    //如果 age == person.age 这个条件不满足,那就显示"年龄不正确":
    if (person.age > 0 && person.age < 100) {
        XCTAssert(age == person.age , @"年龄不正确") ;
    } else {//如果年龄不符合条件那么我认为你的年龄为 0 ,否则年龄超限:
        XCTAssert(person.age == 0 , @"年龄超限") ;
    }
}

//Performance: 性能 ; 表现 ;

/**
    1.相同的代码重复执行 10 次 , 统计计算时间以及平均时间 ;
    2.性能测试代码一旦写好 , 可以随时测试 ;
 注意:
    "要学会测量!不要猜测!" ---> 不要觉得代码怎么样~写得多就复杂?写得少就简单?测试一下!程序猿一定要用数据说话!
 */
#pragma mark - testPerformanceExample 性能测试
- (void)testPerformanceExample {
    // This is an example of a performance test case.
    // 这是一个性能测试的用例:
    [self measureBlock:^{
        // Put the code you want to measure the time of here.
        // 将需要测量执行时间的代码放在此处:
        NSTimeInterval start = CACurrentMediaTime() ;
        for (int i = 0; i< 10000; i++) {
            [Person personWithDictionary:@{@"name":@"zhangsan" , @"age":@20}] ;
        }
        NSLog(@"^*^ --> %f <-- ^*^" , CACurrentMediaTime() - start) ;
    }];
}

/**
 testLoadPersonAsync 异步加载测试:
    苹果的单元测试的特点:早期的单元测试并不好用!
    1.是串行的:
        1.1 先 setUp --->
            然后 test1
            再 test2
            再 test3 ......
            最后 tearDown 知道真相的我眼泪掉下来.
 */
#pragma mark - testLoadPersonAsync 异步加载测试
- (void)testLoadPersonAsync {
    //Xcode6.0之后进行了改进:
    //创建一个"预期"expectation:
    XCTestExpectation *expectation = [self expectationWithDescription:@"异步加载 Person"] ;
    [Person loadPersonAsync:^(Person *person) {
        
        NSLog(@"-_- --> %@ <-- -_-" , person.name) ;
        
        //标注预期达成:
        [expectation fulfill] ;
    }] ;
    
    //等待预期达成:
    //1.0f'之内预期达成我都认为是合理的:
    [self waitForExpectationsWithTimeout:1.0f handler:^(NSError * _Nullable error) {
        NSLog(@"哈哈哈 --> error = %@ <-- 哈哈哈" , error) ;
        
    }] ;
    
}

@end

Person.h 文件

#import 

@interface Person : NSObject
//name:
@property (nonatomic, copy) NSString *name ;
//age:
@property (nonatomic) NSInteger age ;

//工厂方法:
+ (instancetype)personWithDictionary:(NSDictionary *)dictionary ;
//异步加载个人记录:异步加载数据回调传值都是用的 block!
//手写 block一定要注意格式: void (^) (Person *person)completion ;
+ (void)loadPersonAsync:(void (^) (Person *person))completion ;

@end

Person.m 文件

#import "Person.h"

@implementation Person

+ (instancetype)personWithDictionary:(NSDictionary *)dictionary {
    //[[self alloc] init] ;这里用 self 是方便子类继承这个方法,在搞一个 person 的子类我就不用在写这个方法了!
    Person *obj = [[self alloc] init] ;
    //KVC 方法:
    [obj setValuesForKeysWithDictionary:dictionary] ;
    //为了防止单元测试时年龄非法:
    if (obj.age <= 0 || obj.age > 100) {
        obj.age = 0 ;
    }
    return obj ;
}

//写一个空方法:解决多一个 title : boss 的键值对的问题:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    
}

+ (void)loadPersonAsync:(void (^)(Person *))completion {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1.0f] ;
        
        Person *person = [Person personWithDictionary:@{@"name":@"lisi" , @"age":@20}] ;
        dispatch_async(dispatch_get_main_queue(), ^{
            //在 OC 中写块代码回调的时候一定要写块代码是否存在的判断条件,否则一出问题就会闪退:
            if (completion != nil) {
                //回调回来person 这个对象给我:
                completion(person) ;
            }
        }) ;
    }) ;
}

@end

热爱分享,热爱开源

你可能感兴趣的:(单元测试 Tests 的简介及使用)