文章目录
  1. 1. XCTestCase
    1. 1.1. 测试用例的命名
    2. 1.2. setUp和tearDown
    3. 1.3. XCode的测试用例导航
  2. 2. 普通方法测试
  3. 3. 常用断言
  4. 4. 性能测试
  5. 5. 异步测试
  6. 6. 注意点

Xcode集成了对测试的支持,其中单元测试使用的是XCTest框架 <XCTest/XCTest.h>,良好的单元测试可以提高产品的稳定性,快速定位bug,节省开发时间。

XCTestCase

每个XCode创建iOS的工程中都有一个叫做 工程名Tests 的分组,这个分组里就是XCTestCase的子类,XCTest中的测试类都是继承自XCTestCase。

例如新建一个工程,命名为Demo,就能看到如图:

看一下这个自动创建的文件里都包含了哪些内容

测试用例的命名

XCTest中所有的测试用例的命名都是以test开头的。例如上文中的:

1
2
3
4
- (void)testExample {
// This is an example of a functional test case.
XCTAssert(YES, @"Pass");
}

setUp和tearDown

setUp 是在所有测试用例运行之前运行的函数,在这个测试用例里进行一些通用的初始化工作

tearDown 是在所有的测试用例都执行完毕后执行的

XCode的测试用例导航

测试用例的导航如图,在测试用例的导航里,我们可以运行一组测试用例,也可以运行一个单独的测试用例

可以鼠标右键来新建一组测试用例。

也可以为测试用例添加失败断点来方便我们调试

普通方法测试

例如,新建一个类命名为Model,他有这个方法用来生成10以内的随机数。

1
2
3
-(NSInteger)randomLessThanTen{
return arc4random()%10;
}

于是,测试方法为

1
2
3
4
5
-(void)testModelFunc_randomLessThanTen {
Model * model = [[Model alloc] init];
NSInteger num = [model randomLessThanTen];
XCTAssert(num<10,@"num should less than 10");
}

我们点击如图的左边图标单独运行这个测试用例,当然也可以在上文我提到的导航栏里单独运行。

然后会看到输出表示这个测试用例通过

1
2
3
4
5
6
7
8
9
10
Test Suite 'Selected tests' started at 2015-06-06 05:24:56 +0000
Test Suite 'DemoTests.xctest' started at 2015-06-06 05:24:56 +0000
Test Suite 'DemoTests' started at 2015-06-06 05:24:56 +0000
Test Case '-[DemoTests testModelFunc_randomLessThanTen]' started.
Test Case '-[DemoTests testModelFunc_randomLessThanTen]' passed (0.000 seconds).
Test Suite 'DemoTests' passed at 2015-06-06 05:24:56 +0000.
Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.001) seconds
Test Suite 'DemoTests.xctest' passed at 2015-06-06 05:24:56 +0000.
Executed 1 test, with 0 failures (0 unexpected) in 0.000 (0.001) seconds
Test Suite 'Selected tests' passed at 2015-06-06 05:24:56 +0000.

常用断言

如何判断一个测试用例成功或者失败呢?XCTest使用断言来实现。

最基本的断言:表示如果expression满足,则测试通过,否则对应format的错误。

1
XCTAssert(expression, format...)

其他一些常用的断言:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
XCTFail(format...) //生成一个失败的测试;
XCTAssert(expression, format...) //当expression求值为TRUE时通过;
XCTAssertNil(a1, format...) //为空判断,a1为空时通过,反之不通过;
XCTAssertNotNil(a1, format...) //不为空判断,a1不为空时通过,反之不通过;
XCTAssertTrue(expression, format...) //当expression求值为TRUE时通过;
XCTAssertFalse(expression, format...) //当expression求值为False时通过;
/** 特别注意下这两个断言 */
XCTAssertEqualObjects(a1, a2, format...) //判断相等,条件是[a1 isEqual:a2]值为TRUE时通过,其中一个不为空时,不通过;
XCTAssertEqual(a1, a2, format...) //判断相等,条件是a1 == a2(当a1和a2是 C语言标量、结构体或联合体时使用,实际测试发现NSString也可以);
XCTAssertNotEqualObjects(a1, a2, format...) //判断不等,[a1 isEqual:a2]值为False时通过;
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没有发生具体异常、具体异常名称的异常时通过测试,反之不通过

性能测试

所谓性能测试,主要就是评估一段代码的运行时间,XCTest的性能的测试利用如下格式

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

例如,我要评估一段代码,这段代码的功能是把一张图片缩小到指定的大小。

这段代码如下,这段代码我放在UIImage的类别里。

1
2
3
4
5
6
7
- (UIImage *)imageByScaledToSize:(CGSize)newSize {
UIGraphicsBeginImageContext( newSize );
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}

然后测试用例如下,主要判断resize后是否为nil,并且尺寸是否对。

1
2
3
4
5
6
7
8
9
10
- (void)testPerformanceExample {
UIImage * image = [UIImage imageNamed:@"icon.png"];
[self measureBlock:^{
UIImage *resizedImage = [image imageByScaledToSize:CGSizeMake(100, 100)];
XCTAssertNotNil(resizedImage,@"resized image should not be nil");
CGFloat resizedWidth = resizedImage.size.width;
CGFloat resizedHeight = resizedImage.size.height;
XCTAssert(resizedHeight == 100 && resizedWidth == 100,@"Size is not right");
}];
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
Test Suite 'Selected tests' started at 2015-06-06 05:42:39 +0000
Test Suite 'DemoTests.xctest' started at 2015-06-06 05:42:39 +0000
Test Suite 'DemoTests' started at 2015-06-06 05:42:39 +0000
Test Case '-[DemoTests testPerformanceExample]' started.
~/Desktop/Demo/DemoTests/DemoTests.m:41: Test Case '-[DemoTests testPerformanceExample]' measured [Time, seconds] average: 0.000, relative standard deviation: 40.714%, values: [0.000241, 0.000116, 0.000128, 0.000089, 0.000087, 0.000081, 0.000101, 0.000093, 0.000092, 0.000087], performanceMetricID:com.apple.XCTPerformanceMetric_WallClockTime, baselineName: "", baselineAverage: , maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 0.100, maxStandardDeviation: 0.100
Test Case '-[DemoTests testPerformanceExample]' passed (0.357 seconds).
Test Suite 'DemoTests' passed at 2015-06-06 05:42:40 +0000.
Executed 1 test, with 0 failures (0 unexpected) in 0.357 (0.358) seconds
Test Suite 'DemoTests.xctest' passed at 2015-06-06 05:42:40 +0000.
Executed 1 test, with 0 failures (0 unexpected) in 0.357 (0.358) seconds
Test Suite 'Selected tests' passed at 2015-06-06 05:42:40 +0000.
Executed 1 test, with 0 failures (0 unexpected) in 0.357 (0.360) seconds

异步测试

异步测试的逻辑如下,首先定义一个或者多个XCTestExpectation,表示异步测试想要的结果。然后设置timeout,表示异步测试最多可以执行的时间。最后,在异步的代码完成的最后,调用fullfill来通知异步测试满足条件。

1
2
3
4
5
6
7
- (void)testAsyncFunction {
XCTestExpectation * expectation = [self expectationWithDescription:@"Just a demo expectation,should pass"];
//Async function when finished call [expectation fullfill]
[self waitForExpectationsWithTimeout:10 handler:^(NSError *error) {
//Do something when time out
}];
}

举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void) testAsyncFunction {
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);
}];
}

测试结果

1
2
3
4
5
6
7
8
9
10
Test Suite 'Selected tests' started at 2015-06-06 05:49:43 +0000
Test Suite 'DemoTests.xctest' started at 2015-06-06 05:49:43 +0000
Test Suite 'DemoTests' started at 2015-06-06 05:49:43 +0000
Test Case '-[DemoTests testAsyncFunction]' started.
Test Case '-[DemoTests testAsyncFunction]' passed (1.006 seconds).
Test Suite 'DemoTests' passed at 2015-06-06 05:49:44 +0000.
Executed 1 test, with 0 failures (0 unexpected) in 1.006 (1.007) seconds
Test Suite 'DemoTests.xctest' passed at 2015-06-06 05:49:44 +0000.
Executed 1 test, with 0 failures (0 unexpected) in 1.006 (1.009) seconds
Test Suite 'Selected tests' passed at 2015-06-06 05:49:44 +0000.

注意点

  1. 使用pod的项目中,在XC测试框架中测试内容包括第三方包时,需要手动去设置 Header Search Paths 才能找到头文件 ,还需要设置test target的PODS_ROOT。
  2. Xcode7要使用真机做跑测试时,证书必须配对,否则会报错exc_breakpoint错误
  3. XCTestExpectation 的 fulfill 方法只能调用一次,系统不会帮你检查,如果你调用两次就会出错,而且你经常都找不到错在哪里。