XCTest记录

866 阅读4分钟

测试概念:

单元测试:是检查是否每个代码单元(例如类或函数)是否能产⽣预期 的结果。

单元测试是独⽴运⾏的,不依赖于其他模块或组件。

UI测试

  • 属于端到端测试,是从应⽤程序启动到结束的测试过程。
  • 完全按照⽤户与应⽤程序交互的⽅式来复制与应⽤程序的交互。
  • ⽐单元测试慢得多,运⾏起来也更消耗资源。

测试范围

测试应涵盖:

  1. 核⼼功能:模型类和⽅法及其与控制器的交互
  2. UI⼯作流程
  3. 特殊的边界条件
  4. Bug处理

测试原则:

FIRST原则:

  1. Fast:测试模块应该是快速⾼效的;
  2. Independent/Isolated:测试模块应该是独⽴相互不影响的;
  3. Repeatable :测试实例应该是可以重复使⽤的,测试结果应该是相同的;
  4. Self-validating:测试应完全⾃动化。输出结果要么是“成功”,要么是“失败”;
  5. Timely:理想情况下,应该在编写要测试的⽣产代码之前编写测试(测试驱动开发)。

BDD Behavior-Driven Development

三个步骤:

  1. Arrange:构建数据,描述代码场景;
  2. Act:编写代码;
  3. Assert :检查代码是否达到预期效果

配置测试工程

首先我们创建一个TestDemo工程,勾选最下面的include test,那么创建工程的时候就会给我床一个test的Target

image.png 而其中UITest.m里面就是我们写测试代码的地方:

image.png

我们知道正常情况下,APP启动是需要走到APPDelegate,然后执行didFinishLaunchingWithOptions方法,这个方法里面我们会有启动页、初始化操作、业务代码,但是在测试的时候我们需要执行didFinishLaunchingWithOptions方法,但是却不需要启动页、初始化等这些操作,只需要执行我们的初始化代码就行,这个怎么设置呢?

在iOS工程中,程序的入口是main.m,在这里面会指定程序UIApplication的代理

image.png

在这里指定了APPDelegate作为程序的代理去执行APP生命周期方面的方法,那么如果在启动测试的时候我们把APPDelegate替换成测试用delegate就可以了,例如下面:

image.png

是因为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开头的测试方法,

image.png

然后command + u运行一下测试用例

image.png

可以发现我们可以在发现每个测试用例都通Test case开始和结束,所以测试用例里面获取到对应的方法的执行时间。

但是测试用例怎么编译到我们的测试工程里面呢?,我们进入到.app目录看一下:

image.png

发现测试用例其实也对于了一个APP,并且在测试机上,测试APP和主APP是两个APP,我们再打开XCTestDemo1UITests-Runner看一下包内容

image.png

发现在测试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帮助我们一边运行一边测试,并且能获取到方法的执行时间