测试概念:
单元测试:是检查是否每个代码单元(例如类或函数)是否能产⽣预期 的结果。
单元测试是独⽴运⾏的,不依赖于其他模块或组件。
UI测试:
- 属于端到端测试,是从应⽤程序启动到结束的测试过程。
- 完全按照⽤户与应⽤程序交互的⽅式来复制与应⽤程序的交互。
- ⽐单元测试慢得多,运⾏起来也更消耗资源。
测试范围
测试应涵盖:
- 核⼼功能:模型类和⽅法及其与控制器的交互
- UI⼯作流程
- 特殊的边界条件
- Bug处理
测试原则:
FIRST原则:
- Fast:测试模块应该是快速⾼效的;
- Independent/Isolated:测试模块应该是独⽴相互不影响的;
- Repeatable :测试实例应该是可以重复使⽤的,测试结果应该是相同的;
- Self-validating:测试应完全⾃动化。输出结果要么是“成功”,要么是“失败”;
- Timely:理想情况下,应该在编写要测试的⽣产代码之前编写测试(测试驱动开发)。
BDD Behavior-Driven Development
三个步骤:
- Arrange:构建数据,描述代码场景;
- Act:编写代码;
- Assert :检查代码是否达到预期效果
配置测试工程
首先我们创建一个TestDemo工程,勾选最下面的include test,那么创建工程的时候就会给我床一个test的Target
而其中UITest.m里面就是我们写测试代码的地方:
我们知道正常情况下,APP启动是需要走到APPDelegate,然后执行didFinishLaunchingWithOptions方法,这个方法里面我们会有启动页、初始化操作、业务代码,但是在测试的时候我们需要执行didFinishLaunchingWithOptions方法,但是却不需要启动页、初始化等这些操作,只需要执行我们的初始化代码就行,这个怎么设置呢?
在iOS工程中,程序的入口是main.m,在这里面会指定程序UIApplication的代理
在这里指定了APPDelegate作为程序的代理去执行APP生命周期方面的方法,那么如果在启动测试的时候我们把APPDelegate替换成测试用delegate就可以了,例如下面:
是因为XCTestDemo1UITests的父类是XCTestCase,而XCTestCase的父类是XCTest,当走测试工程时候程序会加载XCTest类,如果不走测试工程时候就不会走,如果不加载XCTest类,那么通过NSClassFromString(@"XCTest")就为空,直接走正常APPDelegate就行了
如果是swift的话,本质上是跟OC一样,但是main.m需要我们自己去创建,创建完成后在main.m里面写上下面代码用于替换APPDelegate
import UIKit
// 相当于OC的main函数体执行
let cls = NSClassFromString("XCTest") != nil
let appDelegateClassName = cls ? "TestAppDelegate" : NSStringFromClass(AppDelegate.self)
let args = UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc))
UIApplicationMain(CommandLine.argc, args, nil, appDelegateClassName)
链接XCTest动态库
我们知道可以在APPUITest.m里面写上test开头的测试方法,
然后command + u运行一下测试用例
可以发现我们可以在发现每个测试用例都通Test case开始和结束,所以测试用例里面获取到对应的方法的执行时间。
但是测试用例怎么编译到我们的测试工程里面呢?,我们进入到.app目录看一下:
发现测试用例其实也对于了一个APP,并且在测试机上,测试APP和主APP是两个APP,我们再打开XCTestDemo1UITests-Runner看一下包内容
发现在测试APP的包里面除了在framework里面多了几个动态库外跟普通APP是一样的,我们可以猜测一下就是因为测试APP里面多了几个这样的动态库,所以才能执行测试用例
我们找一下动态库的路径,打开XCode安装目录,找到Content路径,然后在里面搜索XCTest.framework,找到路径:
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks/XCTest.framework
然后我们创建一个config文件,将该动态库导入到项目中,导入动态库的方式有两种: 两种都要先导入动态库的头文件
HEADER_SEARCH_PATHS = $(inherited) "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks/XCTest.framework/Headers"
- 第一种:直接传入动态库所在路径,导入framework
// 传统方式,链接动态库
OTHER_LDFLAGS = $(inherited) -F"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks" -framework "XCTest"
//配置主工程有XCTest动态库
LD_RUNPATH_SEARCH_PATHS = $(inherited) "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks"
- 第二种:直接通过觉得路径方式,也就是直接把动态库拖进来的意思
// 路径
SLASH = /
OTHER_LDFLAGS = $(inherited) ${SLASH}/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks/XCTest.framework/XCTest
//配置主工程有XCTest动态库
LD_RUNPATH_SEARCH_PATHS = $(inherited) "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks"
如何编写测试用例
1、XCTestSuite
这个类是用来管理XCTest的类,他可以添加多个testCase,然后管理这些testCase的执行,例如我们导入头文件<XCTest/XCTest.h>后,可以通过下面代码做测试:
// 管理者 XCTestSuite
XCTestSuite *suite = [XCTestSuite defaultTestSuite];
// 初始化 testCase
LoginAppUITests *testCase = [LoginAppUITests new];
// testCase -》 suite
[suite addTest:testCase];
for (XCTest *test in suite.tests) {
[test runTest];
}
上面就是真正的一次单元测试的调用场景,但是通过defaultTestSuite创建的suite只能执行一次测试用例,无法达到随用随调的状态,且会调用系统添加的testCase。另外一个方式可以多次执行,并且只会执行添加的testCase:
//这种方式可以将LoginAppUITests的所有测试用例都添加到suite里面
// XCTestSuite *suiteq = [XCTestSuite testSuiteForTestCaseClass:LoginAppUITests.class];
XCTestSuite *suite = [XCTestSuite testSuiteWithName:@"MyTest"];
[suite addTest:[LoginAppUITests testCaseWithSelector:@selector(testExample)]];
for (XCTest *test in suite.tests) {
[test runTest];
}
这样就能在项目中集成XCTest帮助我们一边运行一边测试,并且能获取到方法的执行时间