自动化测试就是编写测试代码,代替人工去完成业务与模块的测试。目前人工在测试中还是占比很重的,毕竟代码无法验证非逻辑问题,比如UI控件的显示问题等。
测试框架
XCTest 是Apple 官方提供的测试工具框架。Xcode4.x 时代集成了OCUnit,Xcode5.x 时代升级为 XCTest,Xcode7时代增加了UITest工具。当新建一个项目时,勾选了 Unit Tests 和 UI Tests 选项,我们可以到工程里看到多了两个目录:项目名+Tests
和 项目名+UITests
。
如果没有上述说的两个文件,可以添加相应的 target。操作路径为:
File
—>
New
—>
Target
—>
Test
,缺哪个选哪个了,都缺都选。
项目名+Tests
的
.m
文件里有四个方法,这个文件主要做一些逻辑的测试。
项目名+UITests
的
.m
文件里有三个方法,这个文件主要做一些UI的测试。
XCTest 中的测试类都是继承自 XCTestCase,测试用例方法名自定义以test开头。
单元测试
单元测试最显著最简单的用途就是测试某些小功能等,例如,我们写了一个验证一串字符串是否是手机号格式的方法,我们又不能运行整个工程去验证此方法的正误,因为这样太耗时间了,所以我们用到了单元测试,只需要运行一个测试用例即可。
首先我们新建一个NSString的分类NSString+String
文件,在.m
文件里实现方法:
//验证手机号
-(BOOL)isPhone{
if (self.length != 11) {
return NO;
}
NSString *shuzi = @"^(11[0-9])";
NSPredicate *pred = [NSPredicate predicateWithFormat:@"self matches %@",shuzi];
BOOL flag = [pred evaluateWithObject:self];
return flag;
}
这样我们可以在单元测试文件XXXTests.m
里的编写测试用例了。
首先引入分类的头文件
#import "NSString+String.h"
接着在方法testExample
里可以验证isPhone
是否正确了:
如图,可以点击目录右侧的三角形或者测试方法左侧的菱形,都可以单独运行此测试用例来验证你所写的 isPhone 方法
当然了,也可以再写个测试用例
- (void)testIsPhone{
NSString *phone = @"12345678901";
BOOL flag = [phone isPhone];
XCTAssertTrue(flag,@"是手机号");
}
上面的测试用例验证了验证手机号的方法是错误的,所以我们回到 isPhone 方法里看看,判断正则不对,修改如下:
-(BOOL)isPhone{
if (self.length != 11) {
return NO;
}
NSString *shuzi = @"^([1][0-9]{10})";
NSPredicate *pred = [NSPredicate predicateWithFormat:@"self matches %@",shuzi];
BOOL flag = [pred evaluateWithObject:self];
return flag;
}
再次运行测试用例,显示绿色对勾表示测试通过,若是红色叉号表示测试失败。
一般用断言去处理测试结果,不然的话测试运行通过后,不知道最终验证的结果是否是正确的是想要的。
还可以建一个测试类,例如网络接口调试类:
#import
#import "AFNetworking/AFNetworking.h"
@interface NetworkingTest : XCTestCase
@property (nonatomic, strong) AFHTTPSessionManager *AFHTTPManager;
@end
@implementation NetworkingTest
- (void)setUp {
[super setUp];
//初始化
_AFHTTPManager = [AFHTTPSessionManager manager];
_AFHTTPManager.requestSerializer.timeoutInterval = 50;
_AFHTTPManager.securityPolicy.allowInvalidCertificates = YES;
_AFHTTPManager.requestSerializer = [AFJSONRequestSerializer serializer];
[_AFHTTPManager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];
_AFHTTPManager.responseSerializer.acceptableContentTypes=[NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html", nil];
}
- (void)testRequest{
[self.AFHTTPManager GET:@"http://www.weather.com.cn/data/sk/101010100.html" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"返回数据:%@",responseObject);
[[NSNotificationCenter defaultCenter]postNotificationName:@"Test" object:nil];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"hehehe");
XCTAssertFalse(0,@"服务器连接失败");
[[NSNotificationCenter defaultCenter]postNotificationName:@"Test" object:nil];
}];
//该方法监听一个通知,如果在规定时间内正确收到通知则测试通过。
[self expectationForNotification:@"Test" object:nil handler:nil];
//waitForExpectationsWithTimeout是等待时间,超过时间而没等到expectationForNotification方法收到“Test”通知就报错。
[self waitForExpectationsWithTimeout:3 handler:nil];
}
- (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.
}];
}
@end
需要注意的是测试方法主线程执行完就结束,想要查看异步方法返回结果,就要有一些异步测试的特殊方法,这里用到了expectationForNotification:
方法。
这个NetworkingTest
测试类只是简单说明了一下用法,我们可以建新的测试类来调试不同用途的模块或者功能。具体情况具体应对,一切都是为了清晰化而已。
UI测试
用代码写UI测试代码很麻烦,若非特别情况估计没人费劲去手写,还好Xcode为我们提供了录制功能,这里简单介绍一个登录模块测试例子。
图7
是录制演示,当我们启动录制功能后,点击操作界面,随之形成相应的代码,这样就省去了手动写代码的费劲,之后我们可以点击菱形启动测试方法来测试这段UI操作过程。
UI Testing API
- XCUIApplication
当前测试应用Target - XCUIElement
UI测试中任何一个元素都被抽象成一个XCUIElement类型 - XCUIElementQuery
定位查询当前UI中XCUIElement的一个类
- (void)testExample {
XCUIApplication *app = [[XCUIApplication alloc] init];
XCUIElement *phoneTextField = app.textFields[@"phone"];
[phoneTextField tap];
[phoneTextField typeText:@"15312345678"];
XCUIElement *passwordTextField = app.textFields[@"password"];
[passwordTextField tap];
[passwordTextField tap];
[passwordTextField typeText:@"1234"];
XCUIElement *loginButton = app.buttons[@"login"];
[loginButton tap];
[app.alerts[@"\u63d0\u793a"].buttons[@"\u597d\u7684"] tap];
[passwordTextField typeText:@"123"];
[loginButton tap];
}
我们也可以通过identifier获取当前元素XCUIElement类型,例如:
XCUIElement *phoneTextField = [app.textFields elementMatchingType:XCUIElementTypeTextField identifier:@"phone"];
需要手动设置identifier:
本文Demo