单元测试

2017-03-26  本文已影响57人  Karen_

当进行测试时,你会经常使用到Xcode的测试导航(test navigator)。
测试导航(test navigator)是workspace的一部分,使创建、管理、运行和评估测试更加加单。通过点击导航选择栏中的图标进入测试,该图标在问题导航和调试导航之间。
![查看测试用例](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午4.45.25.png)
注意:Xcode 测试目标(target)生成的测试包(test bundle)显示在测试导航(test navigator)处。

![测试目标2](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午4.46.15.png)

绿色为测试通过、红色为不通过

![运行测试2](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午4.46.41.png)

灰色菱形代表性能测试 这也就能说明这里为什么没有通过或是失败的断言点击此处会出现性能测试结果面板,此面板允许设置性能基准以及编辑基线和最大参数
![测试结果面板](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午4.46.50.png)

关于测试文件方法

- (void)setUp {
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
}

- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
}

- (void)testExample {
    // This is an example of a functional test case.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
}

- (void)testPerformanceExample {
    // This is an example of a performance test case.
    [self measureBlock:^{
        // Put the code you want to measure the time of here.
    }];
}

断言

XCTest

XCTFail(format…) 生成一个失败的测试;

XCTAssertNil(a1, format...)为空判断,a1为空时通过,反之不通过;

XCTAssertNotNil(a1, format…)不为空判断,a1不为空时通过,反之不通过;

XCTAssert(expression, format...)当expression求值为TRUE时通过;

XCTAssertTrue(expression, format...)当expression求值为TRUE时通过;

XCTAssertFalse(expression, format...)当expression求值为False时通过;

XCTAssertEqualObjects(a1, a2, format...)判断相等,[a1 isEqual:a2]值为TRUE时通过,其中一个不为空时,不通过;

XCTAssertNotEqualObjects(a1, a2, format...)判断不等,[a1 isEqual:a2]值为False时通过;

XCTAssertEqual(a1, a2, format...)判断相等(当a1和a2是 C语言标量、结构体或联合体时使用,实际测试发现NSString也可以);

XCTAssertNotEqual(a1, a2, format...)判断不等(当a1和a2是 C语言标量、结构体或联合体时使用);

XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...)判断相等,(double或float类型)提供一个误差范围,当在误差范围(+/-accuracy)以内相等时通过测试;

XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判断不等,(double或float类型)提供一个误差范围,当在误差范围以内不等时通过测试;

XCTAssertThrows(expression, format...)异常测试,当expression发生异常时通过;反之不通过;(很变态) XCTAssertThrowsSpecific(expression, specificException, format...) 异常测试,当expression发生specificException异常时通过;反之发生其他异常或不发生异常均不通过;

XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...)异常测试,当expression发生具体异常、具体异常名称的异常时通过测试,反之不通过;

XCTAssertNoThrow(expression, format…)异常测试,当expression没有发生异常时通过测试;

XCTAssertNoThrowSpecific(expression, specificException, format...)异常测试,当expression没有发生具体异常、具体异常名称的异常时通过测试,反之不通过;

XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...)异常测试,当expression没有发生具体异常、具体异常名称的异常时通过测试,反之不通过

特别注意下XCTAssertEqualObjects和XCTAssertEqual。
XCTAssertEqualObjects(a1, a2, format...)的判断条件是[a1 isEqual:a2]是否返回一个YES。XCTAssertEqual(a1, a2, format...)的判断条件是a1 == a2是否返回一个YES。对于后者,如果a1和a2都是基本数据类型变量,那么只有a1 == a2才会返回YES。

示例如下:

- (void)testExample {
    //设置变量和预期效果
    NSUInteger a = 10;NSUInteger b = 15;
    NSUInteger c = 24;
    //执行方法得到
    NSUInteger sum = [self sum:a b:b];
    //断言判定实际值和预期是否符合
    XCTAssertEqual(sum, c,@"测试不通过");
}
-(NSUInteger)sum:(NSUInteger)a b:(NSUInteger)b{
    return a+b;
}

关于性能测试:设置baseline(基准)和stddev(标准偏差)来判断方法是否能通过性能测试。
就比如说关于网络请求通常我们给出一个标准,多长时间内请求到结果,能够接受的偏差是多少。那么就可以设置一个基准盒偏差来验证这个请求。

官方文档中给出

- (void)testDocumentOpening
{
    // 创建一个expectation对象
    XCTestExpectation *documentOpenExpectation = [self expectationWithDescription:@"document open"];

    NSURL *URL = [[NSBundle bundleForClass:[self class]]
                              URLForResource:@"TestDocument" withExtension:@"mydoc"];
    UIDocument *doc = [[UIDocument alloc] initWithFileURL:URL];
    [doc openWithCompletionHandler:^(BOOL success) {
        XCTAssert(success);

        // assert成功后 便会调用expectation的fulfill方法,来触发下面的handler
        [documentOpenExpectation fulfill];
    }];

    // 在case最后使用这一方法,此时单测程序会阻塞到这里;除非达到了超时时间(秒单位)或者是回调函数中调用了fulfill,单测程序才会结束
    // 若是超时情况,则认为case失败;若通过expectation的fulfill触发,则case通过
    [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) {
        [doc closeWithCompletionHandler:nil];
    }];
}
  - (void)testAsynExample {
    XCTestExpectation *exp = [self expectationWithDescription:@"这里可以是操作出错的原因描述。。。"];
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperationWithBlock:^{
        //模拟这个异步操作需要2秒后才能获取结果,比如一个异步网络请求
        sleep(2);
        //模拟获取的异步操作后,获取结果,判断异步方法的结果是否正确
        XCTAssertEqual(@"a", @"a");
        //如果断言没问题,就调用fulfill宣布测试满足
        [exp fulfill];
    }];

    //设置延迟多少秒后,如果没有满足测试条件就报错
    [self waitForExpectationsWithTimeout:3 handler:^(NSError * _Nullable error) {
        if (error) {
            NSLog(@"Timeout Error: %@", error);
        }
    }];
}

这个测试肯定是通过的,因为设置延迟为3秒,而异步操作2秒就除了一个正确的结果,并宣布了条件满足 [exp fulfill],但是当我们把延迟改成1秒,这个测试用例就不会成功,错误原因是 expectationWithDescription:@"这里可以是操作出错的原因描述。。。

异步测试除了使用 expectationWithDescription以外,还可以使用 expectationForPredicateexpectationForNotification

下面这个例子使用expectationForPredicate 测试方法,代码来自于AFNetworking,用于测试backgroundImageForState方法

- (void)testThatBackgroundImageChanges {
    XCTAssertNil([self.button backgroundImageForState:UIControlStateNormal]);
    NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(UIButton  * _Nonnull button, NSDictionary<NSString *,id> * _Nullable bindings) {
            return [button backgroundImageForState:UIControlStateNormal] != nil;
    }];

    [self expectationForPredicate:predicate
              evaluatedWithObject:self.button
                          handler:nil];
    [self waitForExpectationsWithTimeout:20 handler:nil];
}

利用谓词计算,button是否正确的获得了backgroundImage,如果正确20秒内正确获得则通过测试,否则失败。
expectationForNotification 方法 ,该方法监听一个通知,如果在规定时间内正确收到通知则测试通过。

- (void)testAsynExample1 {
    [self expectationForNotification:(@"监听通知的名称xxx") object:nil handler:nil];
    [[NSNotificationCenter defaultCenter]postNotificationName:@"监听通知的名称xxx" object:nil];

    //设置延迟多少秒后,如果没有满足测试条件就报错
    [self waitForExpectationsWithTimeout:3 handler:nil];
}

这个例子也可以用expectationWithDescription实现,只是多些很多代码而已,但是这个可以帮助你更好的理解 expectationForNotification 方法和 expectationWithDescription 的区别。同理,expectationForPredicate方法也可以使用expectationWithDescription实现。

func testAsynExample1() {
    let expectation = expectationWithDescription("监听通知的名称xxx")
    let sub = NSNotificationCenter.defaultCenter().addObserverForName("监听通知的名称xxx", object: nil, queue: nil) { (not) -> Void in
        expectation.fulfill()
    }
    NSNotificationCenter.defaultCenter().postNotificationName("监听通知的名称xxx", object: nil)
    waitForExpectationsWithTimeout(1, handler: nil)
    NSNotificationCenter.defaultCenter().removeObserver(sub)
}

测试失败的调试工具

添加测试断点

![断点调试](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午5.50.42.png)

当测试方法发布故障断言时,此断点停止测试运​​行。这样可以通过在测试代码中设置的断点停止执行,快速找到存在问题的位置。
![测试断点](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午5.51.48.png)

使用命令行运行测试

使用Xcode命令行工具,你可以使用脚本自动构建和测试你的项目。

测试本地OS X的MyApp
xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp -destination 'platform=OS X,arch=x86_64'

模拟器上运行,使用模拟器可以应对不同的外形因素和操作系统版本:
例如 > xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone,0S=7.0'

测试机上运行:
> xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp -destination 'platform=iOS,name=Development test6s

destination参数可以连接在一起,一个命令在指定共享配置目标上执行测试。例如,下面的命令链将前面三个例子结合在一起变成一个命令:

xcodebuild test -project MyAppProject.xcodeproj -scheme MyApp-destination 'platform=OS X,arch=x86_64'-destination 'platform=iOS,name=Development iPod touch'-destination 'platform=Simulator,name=iPhone,OS=9.0'

如果测试失败,xcodebuild返回非零退出代码。

关于命令行运行测试,你需要知道一些基本要素。关于xcodebuild的详细信息,在终端应用窗口使用man:

man xcodebuild

辅助工具

![辅助编辑器](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.06.43.png)

Test Callers category 。如果你刚刚修复导致应用测试失败的方法,也许你希望检查其他测试中调用的该方法是否运行成功。在源代码编辑器中有问题的方法中,打开assistant editor并从菜单中选择 Test Classes category。在弹窗菜单中可以导航到任何调用它的测试方法中,运行这些测试方法确保回归测试。

Test Classes category。它与Test Callers category.类似,但显示的是有测试方法的类的列表,可以导航到你正在编辑的类。

代码覆盖

代码覆盖率是Xcode7的功能,可以在视觉上看到和衡量你的代码测试覆盖率。有了代码覆盖率,你可以确定测试是否符合你的预期。
启用代码覆盖率

Xcode的代码覆盖率由LLVM支持的测试操作。当你启用代码覆盖率,LLVM基于方法和函数调用的频率来收集覆盖数据。代码覆盖率选项可以收集单元测试和UI测试正确性和性能数据,

编辑scheme的测试操作可以启用代码覆盖率。

  1. 在scheme编辑菜单中选择Edit Scheme。
    ![选择Edit Scheme](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.12.41.png)
  2. 选择测试操作。
  3. 启用代码覆盖复选框以收集覆盖数据。
    ![启用代码覆盖](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.13.52.png)

注意:收集代码覆盖率数据会导致性能损耗。无论损耗是否显著,它均会影响执行代码的线性方式,因此在测试运行中启用代码覆盖率,性能结果依然具有可比性。然而,当你正在认真评估测试程序性能时,你应该考虑是否启用代码覆盖率。

代码覆盖率是用来衡量测试价值的工具。它回答了以下问题

当你运行测试时,什么代码真正运行?
多少测试才算足够?
换句话说,你是否设计足够的测试确保你所有的代码都检查了正确性和性能?
代码的哪部分没有被测试?
在测试运行完成后,Xcode采用LLVM覆盖数据并在报告导航中创建覆盖率报告,参见覆盖率面板。它显示了测试的摘要信息,源文件和源文件中的方法列表以及每个文件中的覆盖百分比。
![代码覆盖率](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.15.54.png)

源代码编辑器展示了文件中代码的行数并高亮未执行的代码。它高亮需要覆盖的代码区域而非已经覆盖的区域。

例如,将指针放在-[Calculator input:]方法上,将显示一个按钮,索引到源代码。
![索引代码](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.17.12.png)

覆盖注释在右边显示,显示了在测试中代码某个特定部分被执行的次数。例如:
![显示执行次数](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.18.00.png)
input:方法,在测试中被频繁调用。然而,有部分方法并未被调用。在源代码编辑器中有明显的标记,如图:
![提示](http://ogpt2m9nl.bkt.clouddn.com/屏幕快照 2017-03-26 下午6.18.07.png)

官方文档:关于Xcode测试

上一篇下一篇

猜你喜欢

热点阅读