单元测试iOS OCios收藏

iOS单元测试详解

2017-05-19  本文已影响571人  i顺颂时宜

简介

XCTest:同时支持单元测试和UI测试。
单元测试:
- Kiwi 老牌测试框架
- specta 另一个BDD优秀框架
- Quick 三个项目中Star最多,支持OC和Swift,优先推荐。

UI测试:
- KIF 基于XCTest的测试框架,调用私有API来控制UI,测试用例用Objective C或Swift编写。

本博文讲述使用框架XCTest及Xcode工具进行单元测试,编写测试代码,以及如何让你的代码更容易单元测试(可能是重构代码的不归路)。

参考链接

官方文档讲述进行单元测试及UI测试详细信息,对如何进行测试,及写可测试代码有简单说明。
单元测试基础的单元测试。
自动化测试讲述UI测试及测试三方框架的使用,及写可测试代码策略。
更轻量的ViewController为测试ViewController做铺垫。
测试ViewController测试ViewController及相应的辅助测试工具。

XCTest测试

1. 测试基础

注意:首次执行性能测量测试时,XCTest始终报告失败,因为未设置评估基准。

UI测试的工作原理是通过查询应用程序的UI对象,合成事件并将其发送到这些对象,并提供丰富的api,使您能够检查UI对象的属性和状态,将其与预期状态进行比较。UI测试使您能够查找应用程序的UI并与其进行交互,以验证UI元素的属性和状态。

UI测试包括UI录制,是帮助你快速编写UI测试的好方法。

2. 测试从哪开始
3. 新建测试Target 及 测试类

Test navigation中分层显示出项目中包含的测试Target,测试类,以及测试方法。


Test navigation
4. 编写测试方法

测试方法是以前缀test开头的测试类的实例方法,不需要参数,返回void,例如-(void)testExample()。测试方法在您的项目中执行代码,使用XCTest框架提供的断言来呈现Xcode显示的测试结果。如果该代码不产生预期结果,则使用一组断言(XCTAssert)API报告失败。例如,函数的返回值可能会与预期值进行比较。

-(void)testUnconditionalFail {
//1. Unconditional Fail:无条件失败当直接到达特定的代码分支指示失败时使用。
       XCTFail(@"无条件失败....");

//2.Boolean Tests
    BOOL a = NO;
    XCTAssert(a,@"失败时提示:a == false");
    XCTAssertTrue(a,@"失败时提示:a == false");
    XCTAssertFalse(a,@"失败时提示:a == true");

//3.基础数据类型
    NSInteger b = 1;
    NSInteger c = 1;
    NSInteger d = 2;
    XCTAssertEqual(b, c, @"失败时提示:b!= c");
    XCTAssertGreaterThan(d, c,@"失败时提示:d < c");
    XCTAssertEqualWithAccuracy(c, d, 1,@"失败时提示:c和d的误差的绝对值大于1");

//4.对象类型
    NSString *nameA = @"nameA";
    NSString *nameB = @"nameB";
    XCTAssertEqualObjects(nameA, nameB,@"失败时提示:nameA != nameB");
    XCTAssertNil(nameA,@"失败时提示:nameA != nil");
    
//5. Exception Tests
    NSArray *array = @[];
    XCTAssertThrows(array[0],@"失败时提示:array[0]没有抛出异常");
    XCTAssertNoThrow(array[0],@"失败时提示:array[0]抛出异常");
    XCTAssertThrowsSpecific(array[0], NSException,@"失败时提示:array[0]没有抛出NSException异常");
    XCTAssertThrowsSpecificNamed(array[0], NSException,@"NSRangeException",@"失败时提示:array[0]没有抛出名为NSRangeException的NSException异常");
}
//
-(void)setUp {
    [super setUp];
    // 初始化代码放在这里. 在调用这个类的每个测试方法之前都要调用.
}
//
-(void)tearDown {
    // 销毁代码放在这里. 在调用这个类的每个测试方法之后都要调用.
    [super tearDown];
}

您可以选择添加到setUp和tearDown的类方法中,在类中的所有测试方法之前和之后运行。

//
+(void)setUp {
    [super setUp];
    // 初始化代码放在这里. 在调用这个类的所有测试方法之前调用.
}
//
+(void)tearDown {
  // 销毁代码放在这里. 在调用这个类的所有测试方法之后调用.
    [super tearDown];
}

以下是MZBayTests测试类的基本结构

//
-(void)setUp {
    [super setUp];
    // 初始化代码放在这里. 在调用这个类的每个测试方法之前都要调用.
}
//
-(void)tearDown {
    // 销毁代码放在这里. 在调用这个类的每个测试方法之后都要调用.
    [super tearDown];
}
//
-(void)testExample {
    //这是一个功能测试用例的例子。
   //使用XCTAssert和相关函数来验证您的测试是否产生正确的结果。
}
//
-(void)testPerformanceExample {
  //这是一个性能测试用例的例子。
   [self measureBlock:^ {
       //把你想要测量运行的时间的代码放在这里。
  }];
}

单元测试中包含普通测试,性能测试,异步测试。

- (void)testModelFunc_randomLessThanTen{
    
    Model * model = [[Model alloc] init];
    
    NSInteger num = [model randomLessThanTen];
    
    XCTAssert(num < 10,@"失败时提示: num should less than 10");
}
-(void)testAdditionPerformance {

    [self measureBlock:^{
       //把你想要测量运行的时间的代码放在这里。
        
          for (NSInteger index = 0; index < 100000; index ++) {
            NSString *str = [@((index+1) % 100) description];
        }

    }];
}
- (void)testAsyncFunction{
    //创建一个XCTestExpectation对象。
    //这个测试只有一个,可以等待多个XCTestExpectation对象。
    
    XCTestExpectation * expectation = [self expectationWithDescription:@"Just a demo expectation,should pass"];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
         NSLog(@"Async test");
        XCTAssert(YES,"should pass");
         //完成相应操作后调用fulfill  这将导致-waitForExpectation
        [expectation fulfill];
    });
    
     //测试将在此暂停,运行runloop,直到超时调用 或所有的expectations都调用了fulfill方法。
    [self waitForExpectationsWithTimeout:0.5 handler:^(NSError *error) {
        //Do something when time out关闭文件等操作
    }];
}
5. 运行测试及查看测试结果
测试结果

点击Source editor中testAdditionPerformance测试方法左侧灰色图标,进行最大标准差和baseline的设置。还可查看性能测试运行10次代码所花费的时长。


step1.性能测试

输入Max STDDEV(最大标准差)和baseline并保存。


step2.性能测试
在Reports navigation中,选中你要查看的测试即可查看更详细的测试结果信息。
Reports navigation
6. 代码覆盖率

Xcode中的代码覆盖是LLVM提供的的测试选项。 当您启用代码覆盖时,LLVM将根据调用方法和函数的频率,对代码收集覆盖数据。 代码覆盖选项可以收集数据以报告正确性和性能的测试,无论是单元测试还是UI测试。

Reports navigator中 Coverage 菜单中可以查看代码覆盖的相关数据。

Coverage

用鼠标选中 - [Calculator input:]方法,将显示一个按钮,点击该按钮将带您进入带注解的源代码。


Coverage

Source editor显示文件中每行代码,在测试期间特定部分代码被调用的次数的注解在Source editor右侧绘制。


Coverage

并突出显示未执行的代码。 它突出了需要覆盖的代码领域,而不是已经涵盖的领域。


Coverage
7. 别人家的测试

FMDBTempDBTests.m的实现


FMDBTempDBTests.m

FMDBTempDBTests的子类只要实现populateDatabase:db方法就配置好测试所需要的数据,可以直接写测试代码了。


FMDatabaseQueueTests.m

编写测试代码

让你的代码更容易单元测试

  1. 将业务逻辑移到 Model 中,查找一个用户的目前的优先事项的列表,做为User的一个Category方法,而不是vc中直接写。
  2. 创建Store类,Store 对象会关心数据加载、缓存和设置数据栈。它也经常被称为服务层或者仓库。
  3. 把网络请求逻辑移到 Model 层,要在 view controller 中做网络请求的逻辑。取而代之,你应该将它们封装到另一个类中。这样,你的 view controller 就可以在之后通过使用回调(比如一个 completion 的 block)来请求网络了。这样的好处是,缓存和错误控制也可以在这个类里面完成。
  4. 把View 代码移到 View 层。不应该在 view controller 中构建复杂的 view 层次结构。你可以使用 Interface Builder 或者把 views 封装到一个 UIView 子类当中。
  5. 通讯:其他在 view controllers 中经常发生的事是与其他 view controllers,model,和 views 之间进行通讯。这当然是 controller 应该做的,但我们还是希望以尽可能少的代码来完成它。

这块内容很空洞,可以参考更轻量的ViewController来做。

上一篇下一篇

猜你喜欢

热点阅读