Fork me on GitHub

iOS单元测试入门

简单介绍

本文主要是讲 XCTest 的使用

我们知道在程序的开发中,单元测试是相当的重要,废话不多说,直接动手吧。

  1. 我们在创建 Xcode 项目的时候全部勾选的话默认会创建 XCTestDemoTests XCTestDemoUITests 这 2 个模块。现在主要讲解 XCTestDemoTests的使用。其中的模板代码如下:
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
// 每次测试前调用,可以在测试之前创建在test case方法中需要用到的一些对象等
// Put setup code here. This method is called before the invocation of each test method in the class.
- (void)setUp {
}

// 每次测试结束时调用tearDown方法
// Put teardown code here. This method is called after the invocation of each test method in the class.
- (void)tearDown {
}

// 单元测试方法的例子
// 我们可以自定义添加 - (void)testxxxx{} 方法,- (void)testxxx 类似的方法在启动测试的时候都会自动跑。
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
- (void)testExample {

}

// 性能测试方法,通过测试block中方法执行的时间,比对设定的标准值和偏差觉得是否可以通过测试
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
  1. XCTAssert 的使用,在单元测试中,我们通常使用 XCTAssert 相关的方法,方法有如下:
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
// 通用断言,为 true 就通过测试
XCTAssert(false);

// 为 true 就通过测试
XCTAssertTrue(false);

// 为 false 就通过测试
XCTAssertFalse(true);

// 相等就通过测试
XCTAssertEqual(1, 2);
// 不相等就通过测试
XCTAssertNotEqual(0, 0);

// 相差的值在精确度范围内就通过测试
XCTAssertEqualWithAccuracy(10, 12, 1);
// 相差的值不在精确度范围内就通过测试
XCTAssertNotEqualWithAccuracy(10, 12, 1);

// 为 nil 就通过测试
XCTAssertNil(nil);
// 不为 nil 就通过测试
XCTAssertNotNil(nil);

// 直接不通过测试,可以自己判断是否加 XCTFail();
XCTFail();
  1. 我们可以使用 ❀ + U 来启动全部单元测试 ❀ + 6 切换到测试模块 这个和 Xcode 的版本有关 或者使用下图的方法来启动测试:

16f2e3826d2f848e
16f2e37b010ab95f

  1. 如何写测试用例呢?

比如我们在开发框架的时候,一般会写一些测试用例。下面我们就模拟写一个简单的框架,同时我们写一些测试用例。如:我们要写一个获取 URL 中的参数的方法。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSString (BMURLParams)

@property (nonatomic, copy, readonly) NSDictionary *bm_URLParams; ///< URLParams

@end

NS_ASSUME_NONNULL_END
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#import "NSString+BMURLParams.h"

@implementation NSString (BMURLParams)

- (NSDictionary *)bm_URLParams {
NSRange range = [self rangeOfString:@"?"];
if (range.location == NSNotFound) {
return nil;
}

NSString *propertys = [self substringFromIndex:(range.location+1)];
NSMutableDictionary *tempDic = @{}.mutableCopy;
[[propertys componentsSeparatedByString:@"&"] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSArray *dicArray = [obj componentsSeparatedByString:@"="];
if (dicArray.count > 1) {
tempDic[dicArray[0]] = dicArray[1];
}
}];
return tempDic;
}

@end
  1. 我们在 XCTestDemoTests 文件中,实现导入 #import "NSString+BMURLParams.h" 同时加上如下的方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)test_URLParams {
XCTAssert(@"https://www.baidu.com/s".bm_URLParams == nil);

XCTAssert([(@"https://www.baidu.com/s?name=jck".bm_URLParams)
isEqualToDictionary:(@{@"name" : @"jck"})]);

XCTAssert([(@"https://www.baidu.com/s?name=jack&type=1".bm_URLParams)
isEqualToDictionary:(@{@"name" : @"jack",
@"type" : @"1"})]);

XCTAssert([(@"https://www.baidu.com/s?name=jack&type=1&user=80222".bm_URLParams)
isEqualToDictionary:(@{@"name" : @"jack",
@"type" : @"1",
@"user" : @"80222"})]);

}
  • 跑一下测试用例,发现如下的效果

16f2e3f3e4bde6d3

说明我们的用例全部通过了,当然这里的用例比较少,我们可以加上各种可能的情况。现在我们把获取参数的代码故意写错为如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "NSString+BMURLParams.h"

@implementation NSString (BMURLParams)

- (NSDictionary *)bm_URLParams {
NSRange range = [self rangeOfString:@"?"];
if (range.location == NSNotFound) {
return nil;
}

NSString *propertys = [self substringFromIndex:(range.location+1)];
NSMutableDictionary *tempDic = @{}.mutableCopy;
[[propertys componentsSeparatedByString:@"&"] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSArray *dicArray = [obj componentsSeparatedByString:@"="];
if (dicArray.count > 1) {
tempDic[dicArray[1]] = dicArray[0];
}
}];
return tempDic;
}
@end

在跑一下单元测试就会有如下的效果:

16f2e4371a72125b

性能测试

现在我们模拟测试网络接口的返回速度是否复合我们的预期,实现我们创建应该发送网络请求的类 BMRequestManager, 代码如下:

1
2
3
@interface BMRequestManager : NSObject
+ (void)getDataWithSuccBlock:(dispatch_block_t)block;
@end
1
2
3
4
5
6
7
@implementation BMRequestManager
+ (void)getDataWithSuccBlock:(dispatch_block_t)block {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
!block ? : block();
});
}
@end
  • 我们编写测试用例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)testExampleRequest {

// 1、创建 XCTestExpectation
XCTestExpectation *exp = [self expectationWithDescription:@"这个请求太慢了"];

// 2、具体的网络请求
[BMRequestManager getDataWithSuccBlock:^{
// 收到数据了
// 发送 fulfill 消息
[exp fulfill];
}];

// 3、定义预期需要在 xxx 时间内获取到数据
NSTimeInterval time = 15;

//4、如果超过 xxx 时间就报错
[self waitForExpectationsWithTimeout:time handler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}

由于我们上面设置的预期时间是 15 秒,但我们实际我们 6 秒就获取到了数据,使用我们在跑用例的时候用如下效果:

16f31f2475a39adb

如果我们设置的预期时间是 5 秒,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)testExampleRequest {

// 1、创建 XCTestExpectation
XCTestExpectation *exp = [self expectationWithDescription:@"这个请求太慢了"];

// 2、具体的网络请求
[BMRequestManager getDataWithSuccBlock:^{
// 收到数据了
// 发送 fulfill 消息
[exp fulfill];
}];

// 3、定义预期需要在 xxx 时间内获取到数据
NSTimeInterval time = 5.0;

//4、如果超过 xxx 时间就报错
[self waitForExpectationsWithTimeout:time handler:^(NSError * _Nullable error) {
if (error) {
NSLog(@"Timeout Error: %@", error);
}
}];
}
  • 跑用例的时候有如下效果:

16f31f35a791a329

说明用例不通过。

  • 当然我们也可以测试其他的一些代码的执行时间和预期时间来比较,看代码是否通过预期时间。

参考

- END -
扫一扫上面的二维码图案,加我微信

温馨提示:博客图片放在 GitHub ,若无法显示,科学上网就好了。

特别声明:若无特殊声明均为原创,转载请注明,侵权请联系

版权声明:署名-非商业性使用-禁止演绎 4.0 国际