一 :前言
很多的开发者 都听说过单元测试,但是不可否认 很多开发者 在实际开发中很少使用这个 单元测试。 大部分人想我自己把工程跑起来 一步步 按照 流程来测试 就行了 。在新建任何一个 工程时 很多小伙伴 对如下图 所示的 的 Include Unit Tests 和 Include UI Tests 感到疑惑。
其实这两个 就是 Xcode 自带的 UnitTest 。
百度百科 对于单元测试 的定义 如下
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。
二: 为什么需要使用 单元测试
1.0 设想你在在一个庞大的工程里面加个了 小功能 A ,按照我们前面的说法,是不是每次都要把工程跑起来,然后 去到 A处 然后进行测试。甚至如果我们仅仅是想 测试一个 接口 返回的参数 而这个接口又需要使用到现有工程中的很多参数 等 如果使用 单元测试的话 就不用每次去 把整个工程跑起来,只需要跑你测试的部分。
2.0 我们可以使用单元测试测 某个方法的耗时和性能,单次 和 多次运行的整体对比的。当然你可以在 方法执行前 获取时间 ,方法结束 后获取时间等方式 获取时间消耗 姑且这样写 麻烦不说 但是如何 计算 CPU占用这些消耗呢?当然我们可以使用instrument 来做更专业的测试。 相对而言 单元测试更加便捷 和 方便使用给我们省不少事。
三 :如何做单元测试
本文只介绍Xcode 自带的 Unit Tests 和 UI Tests
代码下载地址: https://gitee.com/DeLongYang/Performace_Test 下的 UnitTestDemoTests 文件夹就是了。
水平有限 ,如有 不对 希望大家 不吝指正。工程需要安装 Pods 。
第一 如何添加单元测试
1.0 新建工程时 也就是 前言中的那两个选项 勾选就可以了。
2.0 在现有的 工程中 添加单元测试 File ->New ->Target 在选项卡中 选择 iOS UI Testing Bundle 和 iOS Unit Testing Bundle 这两个 任选一个添加给 指定的工程就可以了。
下面我们分 UI Testing Bundle 测试 和 UITest 和 性能测试来 分别阐述。
第二: iOS Unit Tests Bundle
1.0 测试方法
打开 UnitTestDemoTestsTests.m 文件 ,如下图所示 鼠标光标选中菱形区域 就会 显示这个播放的状态,然后
点击 这个播放的状态 就会测试这个方法。 或者 选择左边 testIsChinese 右边也会出现 黑色的小播放按钮 点击也可以测试。
2.0 添加测试方法
下面这些是 系统自带的方法 苹果都给了我们注释。
- (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.
}
这是 我们做性能测试的方法 把要测试的方法 放进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.
for (int i =0; i<1000; i++) {
NSLog(@"this is a example");
}
}];
}
注意!! 所有需要测试的方法名以 test 开头 。 刚添加进去的方法可能左上角的 菱形按钮 不会立即显示 ,你可以 Comand + U Build 这个测试文件一下 就可以了 或者 选中 - (void)testPerformanceExample 测下这个方法就可以了 或者 从 图b 中 3 处 测试都可以。
整体的测试方法 和 Java 中的Junit 有点类似 。 测试没通过 左边就会有一个 大红叉 ,能通过的方法 就会有一个 绿色的钩。
- (void)testExample {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
NSLog(@"自定义测试testExample");
int a = 3;
XCTAssertTrue(a == 0,"a 不能 等于 0");
}
下面是一些 常用的 测试语法。 注意 空 和 nil 还是有区别的 。 读者自己可以测试一下。
- (void)testAssertSyntax
{
// XCTFail(@"this is a fail test"); // 生成一个失败的测试
// XCTAssertNil(@"not a nil string",@"string must be nil"); // XCTAssertNil(a1, format...) 为空判断, a1 为空时通过,反之不通过;
// XCTAssertNil(@"",@"string must be nil"); // 注意@"" 一样无法通过
XCTAssertNil(nil,@"object must be nil");
// XCTAssertNotNil(a1, format…) 不为nil 判断,a1不为 nil 时通过,反之不通过;
// 注意空 和 nil 还是有区别的
XCTAssertNotNil(@"not nil string", @"string can not be nil");
// XCTAssert(expression, format...) 当expression求值为TRUE时通过; expression 为一个表达式
// XCTAssert((2 > 2), @"expression must be true");
XCTAssert((3>2),@"expression is true");
// XCTAssertTrue(expression, format...) 当expression求值为TRUE时通过;>0 的都视为 true
XCTAssertTrue(1, @"Can not be zero");
// XCTAssertFalse(expression, format...) 当expression求值为False时通过;
XCTAssertFalse((2 < 2), @"expression must be false");
// XCTAssertEqualObjects(a1, a2, format...) 判断相等, [a1 isEqual:a2] 值为TRUE时通过,其中一个不为空时,不通过;
XCTAssertEqualObjects(@"1", @"1", @"[a1 isEqual:a2] should return YES");
// XCTAssertEqualObjects(@"1", @"2", @"[a1 isEqual:a2] should return YES");
// XCTAssertNotEqualObjects(a1, a2, format...) 判断不等, [a1 isEqual:a2] 值为False时通过,
// XCTAssertNotEqualObjects(@"1", @"1", @"[a1 isEqual:a2] should return NO");
XCTAssertNotEqualObjects(@"1", @"2", @"[a1 isEqual:a2] should return NO");
// XCTAssertEqual(a1, a2, format...) 判断相等(当a1和a2是 C语言标量、结构体或联合体时使用,实际测试发现NSString也可以);
// 1.比较基本数据类型变量
// XCTAssertEqual(1, 2, @"a1 = a2 shoud be true"); // 无法通过测试
XCTAssertEqual(1, 1, @"a1 = a2 shoud be true"); // 通过测试
// 2.比较NSString对象
NSString *str1 = @"1";
NSString *str2 = @"1";
// NSString *str3 = str1;
XCTAssertEqual(str1, str2, @"a1 and a2 should point to the same object"); // 通过测试
// XCTAssertEqual(str1, str3, @"a1 and a2 should point to the same object"); // 通过测试
// 3.比较NSArray对象
NSArray *array1 = @[@1];
NSArray *array2 = @[@1];
NSArray *array3 = array1;
// XCTAssertEqual(array1, array2, @"a1 and a2 should point to the same object"); // 无法通过测试
XCTAssertEqual(array1, array3, @"a1 and a2 should point to the same object"); // 通过测试
// XCTAssertNotEqual(a1, a2, format...) 判断不等(当a1和a2是 C语言标量、结构体或联合体时使用);
// XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...) 判断相等,(double或float类型)提供一个误差范围,当在误差范围(+/- accuracy )以内相等时通过测试;
// XCTAssertEqualWithAccuracy(1.0f, 1.5f, 0.25f, @"a1 = a2 in accuracy should return NO"); // 测试没法通过
// XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判断不等,(double或float类型)提供一个误差范围,当在误差范围以内不等时通过测试;
XCTAssertNotEqualWithAccuracy(1.0f, 1.5f, 0.25f, @"a1 = a2 in accuracy should return NO"); // 测试通过
// 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没有发生具体异常、具体异常名称的异常时通过测试,反之不通过
}
像 我们前文 中说到的 测试某个接口,源码中的 testRequest 方法。 这里就不贴了 。
/**
测试 网络请求的方法
*/
- (void)testRequest
{
。。。。。。
}
这里 如果是新建的工程的话 运行报错 。提示如下:
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app’s Info.plist file.
请参考http://blog.csdn.net/maxdong24/article/details/53610127中的方案解决。
这是 iOS9 新增加的 传输安全性的规定。
3.0 新建 自定义的 测试文件
Command + N 选中Unit Test Case Class 不是 Cocoa Touch Class !!!! 笔者习惯性思维 好几次选错。新建
UserTest.m 类, 同时在工程中 新建 User 类 ,具体的看源代码中的。因为没有做UI测试 所以 setUp
中的一些方法 我们注释掉不然会 报错。
第二: iOS UI Testing
打开 UnitTestDemoTestsUITests.m 发现 setUp 方法 和 UnitTest 中有些不同。多了一个
self.continueAfterFailure = NO; 和 [[[XCUIApplication alloc] init] launch]; 苹果都有解释 。我们照做就可以了。
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
// 如果发生 测试不通过的情况 ,最好 停止程序的运行
// In UI tests it is usually best to stop immediately when a failure occurs.
self.continueAfterFailure = NO;
// UI 测试必须 等应用 先开启 ,这个方法 可以确保应用的开启 在每个测试方法 测试的时候 。
// UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
[[[XCUIApplication alloc] init] launch];
// In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
先 添加 测试方法 - (void)testLogin 大扩号中是 空白的
- (void)testLogin
{
}
把 光标 放进到这个 大括号里面,看到 如下图 c 的小红点。点击这个录制屏幕的按钮。应用启动。 我们点击
首页的 ViewController 中的两个 userName 和 passWord UITextField 输入相关的内容。 再点登陆 发现 - (void)testLogin的括号中自动生成了代码 。
接着 直接测试 - (void)testLogin 会发现重复 了我们录屏时候的 输入和 输出 包括跳转等等。
注意 在 OC 写的工程中 如果 输入中文 自动生成的测试代码有问题如图 d 所示
- (void)testLoginTwo 就是这种现象 的代码
但是在 swift 工程 中就没有这种问题。 如何解决?
其实我们可以 自己 写 测试 代码 如 - (void)testLoginThree 我们可以打断点 看 打印 app 的结构 。
- (void)testLoginThree
{
XCUIApplication *app = [[XCUIApplication alloc] init];
// 下面这是一种通过 遍历 获取的形式
for (NSInteger i = 0;i < app.textFields.count; i++) {
if ([[app.textFields elementBoundByIndex:i] exists]) {//判断是否存在
[[app.textFields elementBoundByIndex:i] tap];//输入框要获取焦点后才能给输入框自动赋值
if (i == 0 ) {
// 给 第一个userName 自动赋值 你好
[[app.textFields elementBoundByIndex:i] typeText:@"你好"];
}
// 给 第二个userPass 自动赋值 德龙
if (i == 1) {
[[app.textFields elementBoundByIndex:i] typeText:@"德龙"];
}
}
}
}
下面看另外一种写法 这种发放比较 特别
我们之所以 能使用 textFields[@"username:"]; 和 app.buttons[@"login"] 这种语法 来找到对应的控件 是因为
usernameTextField 的Placeholder 是 username: 而 登录按钮的title 是 login !!!storyboard中都有设置 。 不然会报错。
- (void)testLoginFour
{
XCUIApplication *app = [[XCUIApplication alloc] init];
// //XCUIElement 这是 UI 元素的代理。元素都有类型和唯一标识。可以结合使用来找到元素在哪里,如当前界面上的一个输入框
XCUIElementQuery *textFields = app.textFields;
// XCUIElement *usernameTextField = [textFields objectForKeyedSubscript:@"username:"];
XCUIElement *usernameTextField = textFields[@"username:"];
[usernameTextField tap];
[usernameTextField typeText:@"德龙"];
XCUIElement *passwordTextField = app.textFields[@"password:"];
[passwordTextField tap];
[passwordTextField tap];
[passwordTextField typeText:@"杨"];
//
[app.buttons[@"login"] tap];
//登录成功后的控制器的title为loginSuccess,只需判断控制器的title时候一样便可判断登录是否成功
NSLog(@"title is %@",app.navigationBars.element.identifier);
XCTAssertEqualObjects(app.navigationBars.element.identifier, @"loginSuccess");
// 延时 3s 再消失
XCUIElement *window = [app.windows elementBoundByIndex:0];
[window pressForDuration:3];
}
核心类是 XCUIElement 和 XCUIElementQuery 这两个类 , 可以看下这两个类 看下是如何设计的。 这种设计思路 笔者认为 和FMDB 中的 FMResult 等的设计思路是差不多的。 另外还有要给 TableViewController 的测试
这里就不再赘述了。
第三: 性能测试
我们回到 UnitTestDemoTestsTests.m 中的 - (void)testPerformanceExample 方法 写入
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
for (int i =0; i<1000; i++) {
NSLog(@"this is a example");
}
}];
}
第一次运行 会出现 改方法的耗时 ,然后 提示 noBase ... 点进这个提示 我们可以设置base 设置之后再运行就点击左边的 灰色的 钩 会出现下图e 所示的
对比 。 耗时和CPU 消耗等,以及多次的 对比都有 ,比较简单 。
到这就结束了 , 满地卖萌打滚求喜欢 ,各位看官打赏打赏?
二: 参考文档
//www.greatytc.com/p/8bbec078cabe