本文简介
- 1.UnitTests
- 2.XCTest 单元测试
- 3.OCMock 单元测试
- 4.UITests UI 测试
1.UnitTests
- 在计算机编程中,单元测试(又称为模块测试,Unit Testing)是针对程序模块(软件设计的最小单位)来进行正确性校验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
- 通常来说,程序员每修改一次代码就会修改某个单元,那我们就可以对这个单元做修改的验证(单元测试),在编写程序的过程中前后很可能要进行多次单元测试,已证实程序达到软件规格书(产品需求)要求的工作目标,而且没有程序错误。虽然单元测试不是什么必须的,但也不坏,这牵扯到项目管理的政策决定。
- 单元测试可以方便测试一些功能是否正常运行,调试接口是否正确使用。有时候你可能是为了测试某一个网络接口,然后每次都重新启动并且经过很多操作之后测试到了那个网络接口,如果使用了单元测试,就可以直接测试那个方法,相对方便很多,比如由于修改较多,我们想测试一下分享功能是否正常,这时候就有用了,而不是重新启动程序,进入到分享界面,点击分享,填写分享内容。其实单元测试并没有降低我们打代码的效率,我们可以在单元测试通过了,直接用到相应的地方。
- 当然单元测试也有一些高级的作用,比如自动发布、自动测试(特别在一些大的项目,以防止程序被误改或引起新的问题)。
1.1 iOS中的单元测试框架
- XCTest 是苹果自带的测试框架。
- GHUnit 是一个可视化的测试框架,有了它,你可以点击APP来决定测试哪个方法,并且可以点击查看测试结果等。
- OCMock是模拟某个方法或者属性的返回值,你可能会疑惑为什么要这样做?使用模型生成的模型对象,再传进去不就可以了?答案是可以的,但是有特殊的情况,比如你测试的方法A,方法A里面调用到了方法B,而且方法B是有参数传入,但又不是方法A所提供,这时候,你可以使用OCMock来模拟方法B返回的值。在不影响测试的情况下,就可以这样去模拟。除了这些,在没有网络的情况下,也可以通过OCMock模拟返回的数据。
- UITests 是通过代码化来实现自动点击界面,输入文字等功能。靠人工操作的方式来覆盖所有测试用例是非常困难的,尤其是加入新功能以后,旧的功能也要重新测试一遍,这导致了测试需要花非常多的时间来进行回归测试,这里产生了大量重复的工作,而这些重复的工作有些是可以自动完成的,这时候UITests就可以帮助解决这个问题了。
1.2单元测试思路
- 1,单元测试是以代码测试代码。不是靠NSLog来测试,NSLog是程序员用眼睛看的笨方法,而是使用断言来测试的,提前预判条件必须满足。
XCTAssert(expression, ...)
XCTAssert(条件, 不满足条件的描述)
- 2.单元测试与应用程序开发属于共存关系,而非嵌入关系,所以必须创建一个单独的测试目标。
- 3.可以在单元测试类中编写单独的测试用例方法,这些方法与普通的方法类似,但是方法名称必须以test开头,且不能有参数,不然不会识别为测试方法。
- 4.测试方法可以直接写在
- (void) testExample
中,或者写在以test开头的测试用例方法中。 - 5.单元测试需要在真机上运行,为了能够在设备中真实地运行应用程序用例,需要安装开发配置文件(development provision file)。
- 6.需要注意,在应用程序上运行单元测试用例并不是一个交互过程,所有的运行控制(包括提供值)都由测试用例自身掌握。
- 7.不是所有的方法都需要测试,例如私有方法不需要测试,只有暴露在.h中的方法需要测试。
- 8.一般而言,代码的覆盖度大概在50%-70%。
2、XCTest单元测试
2.1 测试使用方法
-
单元调试操作,两种方法,按快捷键Command + U 进行单元测试,这个快捷键是全部测试。
-
调试可以在断点处调试,也可以在函数部分调试,错误提示是在断点处显示,不会在平台展示。
2.2测试类中的方法
- 测试方法
- (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.
// 测试性能的方法,有 Instrument 调试工具之后,其实这个没毛用
[self measureBlock:^{
// Put the code you want to measure the time of here.
// 需要测试性能的代码
}];
}
2.3 测试函数
- 测试函数
// 生成一个失败的测试
XCTFail(format…);
// 为空判断
// 为空判断,a1 为空时通过,反之不通过
XCTAssertNil(a1, format...);
// 不为空判断,a1 不为空时通过,反之不通过
XCTAssertNotNil(a1, format…);
// 为真判断
// 为真判断,当 expression 求值为 True 时通过
XCTAssert(expression, format...);
// 为真判断,当 expression 求值为 True 时通过
XCTAssertTrue(expression, format...);
// 为假判断,当 expression 求值为 False 时通过
XCTAssertFalse(expression, format...);
// 相等判断
// 相等判断,[a1 isEqual:a2] 值为 True 时通过,其中一个不为空时,不通过
XCTAssertEqualObjects(a1, a2, format...);
// 不等判断,[a1 isEqual:a2] 值为 False 时通过
XCTAssertNotEqualObjects(a1, a2, format...);
// 相等判断,当 a1 和 a2 是 C 语言标量、结构体或联合体时使用,a1 == a2 值为 True 时通过
XCTAssertEqual(a1, a2, format...);
// 不等判断,当 a1 和 a2 是 C 语言标量、结构体或联合体时使用
XCTAssertNotEqual(a1, a2, format...);
// 相等判断,double 或 float 类型,提供一个误差范围,当在误差范围(+/-accuracy)以内相等时通过测试
XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...);
// 不等判断,double 或 float类型,提供一个误差范围,当在误差范围以内不等时通过测试
XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...);
// 异常判断
// 异常判断,当 expression 发生异常时通过,反之不通过
XCTAssertThrows(expression, format...);
// 异常判断,当 expression 发生 specificException 异常时通过,反之发生其他异常或不发生异常均不通过
XCTAssertThrowsSpecific(expression, specificException, format...);
// 异常判断,当 expression 发生具体异常、具体异常名称的异常时通过测试,反之不通过
XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...);
// 异常判断,当 expression 没有发生异常时通过测试
XCTAssertNoThrow(expression, format…);
// 异常判断,当 expression 没有发生具体异常、具体异常名称的异常时通过测试,反之不通过
XCTAssertNoThrowSpecific(expression, specificException, format...);
// 异常判断,当 expression 没有发生具体异常、具体异常名称的异常时通过测试,反之不通过
XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...);
2.4测试的基本使用
- 基本使用
- (void)testExample {
NSLog(@"自定义测试 testExample");
int a = 3;
XCTAssertTrue(a == 0, "a 不能等于 0");
}
-
点击播放按钮,开始每个方法的测试
-
出现如下结果,由于我们断言a是等于0的,而a等于3,所以测试没通过。
2.5测试问题解决
- 问题描述:fatal error: 'XCTest/XCTest.h' file not found
- 解决办法
在报错的 Target 中的 Building settings 中 FRAMEWORK_SEARCH_PATHS* 添加
$(PLATFORM_DIR)/Developer/Library/Frameworks
2.6单例测试
- 单例要在并发条件下调试
// 测试是否为单例
- (void)testAudioManagerSingle {
// 要在并发条件下测试
NSMutableArray *managers = [NSMutableArray array];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
QAudioManager *tempManager = [[QAudioManager alloc] init];
[managers addObject:tempManager];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
QAudioManager *tempManager = [[QAudioManager alloc] init];
[managers addObject:tempManager];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
QAudioManager *tempManager = [QAudioManager defaultManager];
[managers addObject:tempManager];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
QAudioManager *tempManager = [QAudioManager defaultManager];
[managers addObject:tempManager];
});
QAudioManager *managerOne = [QAudioManager defaultManager];
// 这里是判断数组中的对象是否一致
[managers enumerateObjectsUsingBlock:^(QAudioManager *obj, NSUInteger idx, BOOL * _Nonnull stop) {
XCTAssertEqualObjects(managerOne, obj, @"QAudioManager is single");
XCTAssertNotEqualObjects(managerOne, obj, @"QAudioManager is not single");
}];
}
2.7性能测试
- 性能测试
测试一段代码(函数、方法)的执行时间,我们通常是用到 CFAbsoluteTimeGetCurrent() 或者 CACurrentMediaTime() 函数,通过差值来计算出时间间隔。
+ (instancetype)personWithDict:(NSDictionary *)dic {
NSString *str1;
for (NSString *str in dic) {
str1 = [str stringByAppendingString:str];
}
str1 = nil;
Person *one = [[self alloc] init];
return one;
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
// 测试性能的方法,有 Instrument 调试工具之后,其实这个没毛用
[self measureBlock:^{
// Put the code you want to measure the time of here.
// 需要测试性能的代码
NSTimeInterval start = CACurrentMediaTime();
// 测试用例,循环10000次,为了演示效果
for (NSInteger i = 0; i < 10000; i++) {
[Person personWithDict:@{@"name":@"zhang", @"age":@20}];
}
// 传统测试代码耗时方法
NSLog(@"%lf, 我是香蕉大大", CACurrentMediaTime() - start);
}];
}
2.8逻辑测试
// 逻辑测试
- (void)testNewPerson {
// 1.测试 name 和 age 是否一致
[self checkPersonWithDict:@{@"name":@"zhou", @"age":@30}];
/** 2.测试出 age 不符合实际,那么需要在字典转模型方法中对 age 加以判断:
if (obj.age <= 0 || obj.age >= 130) {
obj.age = 0;
}
*/
[self checkPersonWithDict:@{@"name":@"zhang", @"age":@200}];
// 3.测试出 name 为 nil 的情况,因此在 XCTAssert 里添加条件:“person.name == nil“
[self checkPersonWithDict:@{}];
// 4.测试出 Person 类中没有 title 这个 key,在字典转模型方法中实现:- (void)setValue:(id)value forUndefinedKey:(NSString *)key {}
[self checkPersonWithDict:@{@"name":@"zhou", @"age":@30, @"title":@"boss"}];
// 5.总体再验证一遍,结果 Build Succeeded,测试全部通过
[self checkPersonWithDict:@{@"name":@"zhou", @"age":@-1, @"title":@"boss"}];
}
// 根据字典检查新建的 person 信息
- (void)checkPersonWithDict:(NSDictionary *)dict {
Person *person = [Person personWithDict:dict];
NSLog(@"%@",person);
// 获取字典中的信息
NSString *name = dict[@"name"];
NSInteger age = [dict[@"age"] integerValue];
// 1.检查名字
XCTAssert([name isEqualToString:person.name] || person.name == nil, @"姓名不一致");
// 2.检查年龄
if (person.age.integerValue > 0 && person.age.integerValue < 130) {
XCTAssert(age == person.age.integerValue, @"年龄不一致");
} else {
XCTAssert(person.age == 0, @"年龄超限");
}
}
2.9网络请求测试
安装 AFNetworking 和 STAlertView
由于测试方法主线程执行完就会结束,所以需要设置一下,否则没法查看异步返回结果。在方法结束前设置等待,调回回来的时候再让它继续执行。
//waitForExpectationsWithTimeout是等待时间,超过了就不再等待往下执行。
#define WAIT do {\
[self expectationForNotification:@"RSBaseTest" object:nil handler:nil];\
[self waitForExpectationsWithTimeout:30 handler:nil];\
} while (0);
#define NOTIFY \
[[NSNotificationCenter defaultCenter]postNotificationName:@"RSBaseTest" object:nil];
- 增加测试方法 testRequest
- (void)testRequest{
// 获得请求管理者
AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
mgr.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",nil];
// 发送 GET 请求
[mgr GET:@"http://www.weather.com.cn/adat/sk/101110101.html" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
XCTAssertNotNil(responseObject, @"返回出错");
NOTIFY // 继续执行
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
XCTAssertNil(error, @"请求出错");
NOTIFY // 继续执行
}];
WAIT //暂停
}
- 有时候我们想测试一下整个流程是否可以跑通,比如获取验证码、登录、上传头像,查询个人资料。其实只要输入验证码就可以完成整个测试。这时候就需要用到输入框了,以便程序继续执行。使用了一个第三方的弹出输入框 STAlertView,前面已经设置。
self.stAlertView = [[STAlertView alloc] initWithTitle:@"验证码"
message:nil
textFieldHint:@"请输入手机验证码"
textFieldValue:nil
cancelButtonTitle:@"取消"
otherButtonTitle:@"确定"
cancelButtonBlock:^{
// 点击取消返回后执行
[self testAlertViewCancel];
NOTIFY // 继续执行
} otherButtonBlock:^(NSString *b) {
// 点击确定后执行
[self alertViewComfirm:b];
NOTIFY // 继续执行
}];
[self.stAlertView show];
3.OCMock单元测试
3.1OCMock
- Mock 测试
Mock 测试是个很神奇而又很酷的技术,在测试过程中,对于一些不容易构造或不容易获取的对象,此时你可以创建一个虚拟的对象(mock object)来完成测试。
例如你可能要尝试 100 次才会返回一个 NSError,通过 mock object 你可以自行创建一个 NSError 对象,测试在出错情况下程序的处理是否符合你的预期。
例如你要连接服务器但是服务器在实验室,你在外工作的时候就无法测试了,这个时候你可以创建一个虚拟的服务器,并返回一些你指定的数据,从而绕过服务器。
例如假设你要访问一个数据库,但是访问过程的开销巨大,这时你可以虚拟一个数据库,并且返回一些自行定制的数据,从而绕过了数据库的访问。
Mock 的思想很简单:没有条件?我们就自行创造条件。
- OCMock
OCMock 是一个用于为 iOS 或 macOS 项目配置 Mock 测试的开源项目,如果目标是 iOS 项目那么生成的是静态库,如果是 macOS 项目生成的是框架。OCMock 其实现思想就是根据要 mock 的对象的 class 来创建一个对应的对象,并且设置好该对象的属性和调用预定方法后的动作(例如返回一个值,调用代码块,发送消息等等),然后将其记录到一个数组中,接下来开发者主动调用该方法,最后做一个 verify(验证),从而判断该方法是否被调用,或者调用过程中是否抛出异常等。
OCMock 官网
iOS Project Setup:在 iOS 项目中配置 OCMock 的教程
erikdoe / ocmock:在 GitHub 上的示例项目,可以参考下其中的一些配置参数
OCMock Download:OCMock 的静态库、框架和工程文件(可以在这里看 OCMock 的源码实现)下载地址,已经打包成 dmg 格式了。
3.2配置OCMock
-
1、下载 OCMock Download 的 dmg 文件,将 iOS library 文件夹中的文件(libOCMock.a 和 OCMock 文件夹)拷贝到要测试的项目根目录下。打开工程,将拷贝的文件添加到项目工程中。
-
2、打开 OCMockDemoTests Target 的 Build Phases,添加 libOCMock.a 到要链接的类库中。
- 3、打开 Build Settings,搜索 Other Linker Flags,设置如下
-force_load
"$(SRCROOT)/OCMock/libOCMock.a"
-ObjC
- 这里的 -ObjC 表示告诉链接器,要把 OC 类和 Category 加载到工程中,但是该设置有 Bug,所以还要用 -all_load 或者 -force_load 来加载静态库中没有加载进来的 Category。如果使用 -all_load 会把所有相关无关的文件都 load 进来,使得目标程序变得更大,所以用 -force_load 来指定要加载的静态库就可以了,下面的 "$(SRCROOT)/OCMock/libOCMock.a" 就是静态库文件在 Finder 中的路径。
-
4、在搜索Header Search Paths,设置如下
- "$(SRCROOT)/OCMock" 给出的是 OCMock 的头文件在 Finder 中的路径,因此该选项告诉编译器应该到哪里去寻找 OCMock 静态库的头文件。
3.3 编写mock测试
- 新建一个 test case class 类,基类为 XCTestCase,命名为 MockTableTests。
- 首先我们测试一下 TableDataSource 的 numberOfRowsInSection 方法是否返回了正确的值,测试代码如下
- (void)testNumberOfRows {
// 创建 Table View 的 DataSource
TableViewCellConfigureBlock cellConfigureBlock = ^(UITableViewCell *cell, NSString *item) {
cell.textLabel.text = item;
};
TableDataSource *tableSource = [[TableDataSource alloc] initWithItems:@[@"1", @"2", @"3"]
CellIdentifier:@"foo"
ConfigureCellBlock:cellConfigureBlock];
// 创建 mock table view
id mockTableView = [OCMockObject mockForClass:[UITableView class]];
// 断言
XCTAssertEqual([tableSource tableView:mockTableView numberOfRowsInSection:0], (NSInteger)3,
@"Mock table returns a bad number of rows in section 0");
}
- 1、首先创建 data source,用于下文中调用 numberOfRowsInSection 方法。注意这里 Table View 中的内容 @[@"1", @"2", @"3"] 是需要我们手动配置的。这里也体现了 mock 的一个局限性,就是 mock object 的关键属性都要我们自己定制,如果要模拟的对象非常的大,那么创建一个 mock object 的成本将远远大于单元测试带来的效益。
- 2、如果要单独测试 numberOfRowsInSection 方法,我们就需要有一个 TableView,因此要通过 OCMockObject 的 mockForClass 类方法来创建一个 mock table view。
-
3、通过 data source 调用方法,并使用断言判断。
如果在测试时,我们只想在控制台中看见这个方法的输出信息,可以点击方法前面的一个小播放按钮
控制台输出
下面来编写一个稍微复杂点的 mock 测试,用来测试 UITableViewDataSource 中的 cellForRowAtIndexPath 方法。
- (void)testCellConfiguration {
// 创建 Table data source
__block UITableViewCell *configuredCell = nil;
__block id configuredObject = nil;
TableViewCellConfigureBlock block = ^(UITableViewCell *a, id b) {
configuredCell = a;
configuredObject = b;
};
TableDataSource *dataSource = [[TableDataSource alloc] initWithItems:@[@"a", @"b"]
CellIdentifier:@"foo"
ConfigureCellBlock:block];
// 创建 mock table view
id mockTableView = [OCMockObject mockForClass:[UITableView class]];
// 设定 mock table view 的行为
UITableViewCell *cell = [[UITableViewCell alloc] init];
[[[mockTableView expect] andReturn:cell] dequeueReusableCellWithIdentifier:@"foo"
forIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// [[[mockTableView stub] andReturn:cell] dequeueReusableCellWithIdentifier:@"foo" forIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// 主动调用 cellForRowAtIndexPath 方法
id result = [dataSource tableView:mockTableView
cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// 验证 mock table view 的行为
[mockTableView verify];
// 断言
XCTAssertEqual(result, cell, @"Should return the dummy cell.");
XCTAssertEqual(configuredCell, cell, @"This should have been passed to the block.");
XCTAssertEqualObjects(configuredObject, @"a", @"This should have been passed to the block.");
}
1、创建 Table data source,用于下文调用 cellForRowAtIndexPath 方法。
2、创建 mock table view。
3、如果 mock table view 调用了 dequeueReusableCellWithIdentifier:@"foo" forIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] 方法,那么就返回上面已经创建好的 UITableViewCell 对象,expect 方法表示该方法必须被调用(见5.)。
4、通过 Table data source 主动调用 cellForRowAtIndexPath 方法,此时会触发 mock table view 调用 dequeueReusableCellWithIdentifier:forIndexPath: 方法。
5、最后要调用 verify 方法,用于验证 mock table view 的行为。如果 mock table view 在某个方法中调用了 expect,那么该方法必须在 verify 之前被调用,否则测试无法通过。如果 mock table view 调用的是 stub,那么 verify 时 OCMock 并不关心该方法是否调用过,只会关心调用过程是否发生异常或有测试被拒绝等。
6、断言,在这里进行各种比较。
4、GHUnit单元测试
4.1 GitHub
可能大家都注意到了,在运行测试后,控制台中的输出可以用惨不忍睹来形容。这时我们可以尝试另一个工具:GHUnit 框架,这个工具是有 GUI 的。
gh-unit / gh-unit:该项目在 GitHub 上的地址。
guide_testing Document:编写测试的参考文档。
4.2 GHUnit使用
- 具体详解见 GHUnit
5、UITests UI测试
5.1UITests
- UITests是一个自动测试UI与交互的Testing组件。它可以通过编写代码、或者是记录开发者的操作过程并代码化,来实现自动点击某个按钮、视图,或者自动输入文字等功能。
- 在实际的开发过程中,随着项目越做越大,功能越来越多,仅仅靠人工操作的方式来覆盖所有测试用例是非常困难的,尤其是加入新功能之后,旧的功能也要重新测试一遍,这导致了测试需要花非常多的时间来进行回归测试,这里产生了大量重复的工作,而这些重复的工作有些是可以自动完成的,这时候UITests就可以帮助解决这个问题了。
5.2测试元素语法
- 1、XCUIApplication
继承 XCUIElement,这个类掌管应用程序的生命周期,里面包含两个主要方法
launch():启动程序
terminate():终止程序
- 2、XCUIElement
继承 NSObject,实现协议 XCUIElementAttributes, XCUIElementTypeQueryProvider
可以表示系统的各种UI元素
- 3、exist
可以让你判断当前的 UI 元素是否存在,如果对一个不存在的元素进行操作,会导致测试组件抛出异常并中断测试
- 4、descendantsMatchingType(type:XCUIElementType)->XCUIElementQuery:
取某种类型的元素以及它的子类集合
- 5、childrenMatchingType(type:XCUIElementType)->XCUIElementQuery:
取某种类型的元素集合,不包含它的子类
这两个方法的区别在于,你仅使用系统的 UIButton 时,用 childrenMatchingType 就可以了,如果你还希望查询自己定义的子 Button,就要用 descendantsMatchingType
-
6、另外 UI 元素还有一些交互方法
- tap():点击
- doubleTap():双击
- pressForDuration(duration: NSTimeInterval):长按一段时间,在你需要进行延时操作时,这个就派上用场了
- swipeUp():这个响应不了 pan 手势,暂时没发现能用在什么地方,也可能是 beta 版的 bug,先不解释
- typeText(text: String):用于 textField 和 textView 输入文本时使用,使用前要确保文本框获得输入焦点,可以使用 tap() 函数使其获得焦点
-
7、XCUIElementAttributes 协议
-
里面包含了 UIAccessibility 中的部分属性
-
- 可以方便你查看当前元素的特征,其中 identifier 属性可用于直接读取元素,不过该属性在 UITextField 中有 bug,暂时不清楚原因
- 8、XCUIElementTypeQueryProvider 协议
-
里面包含了系统中大部分 UI 控件的类型,可通过读属性的方式取得某种类型的 UI 集合,部分属性截图如下
5.3 添加UITests
-
1、如果是新项目,则创建工程的时候可以直接勾选选项,如下图
-
2、如果是已有的项目,可以通过添加 target 的方式添加一个 UI Tests,点击 xcode 的菜单,找到 target 栏
-
在 Test 选项中选择 Cocoa Touch UI Testing Bundle
-
3、这时候 test 组件添加成功,它在项目中的位置如下图所示.
5.4创建测试代码
- 1、手动创建测试代码
-
打开测试文件,在testExample()方法中添加测试代码,如果不知道如何写测试代码,则可以参考自动生成的代码样式
- 2、自动生成测试步骤
-
选择测试文件后,点击录制按钮
*这时候开始进行操作,他会记录你的操作步骤,并生成测试代码,下图就是在一些操作后自动生成的测试代码。
- 3、开始测试
-
点击testExample方法旁边的播放按钮,他就开始进行自动测试了,这时候你会看到APP在自动操作
原文地址:https://www.cnblogs.com/QianChia/p/6379191.html