XCTest测试指南

7,925 阅读5分钟

XCTest是Xcode自带测试框架,可用于MacOS和iOS应用的测试。

XCTest可用于业务逻辑的单元测试,以及模拟用户操作的UI测试。该测试框架集成在Xcode中,可使用swift及Objective-C进行开发,Apple应用开发者开箱即可使用,学习成本非常低。同时还可以把单元测试运行加入CI的一个步骤,把测试代码的通过率及覆盖率作为代码质量的一个参考指标,有助于提高代码质量,减少由于人为疏忽引起的一些bug,降低代码风险。

XCTestCase

当我们在创建MacOS或iOS工程时,我们可以勾选创建xxxTestsxxxUITests的Target; 顾名思义, xxxTests是用来进行业务逻辑代码单元测试的;xxxUITests是用来进行UI测试的。

初创建的测试工程只有一个xxxTests.swift和一个info.plist文件,这里我们主要关心单元测试要怎么编写,所以我们这里主要看一下xxxTests.swift文件里面都是有什么内容:

框架的引入

import XCTest

由于我们是使用XCTest框架,所以在开头我们需要先引入XCTest

测试类的创建

class xxxTests: XCTestCase {

    override func setUp() {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }

    func testPerformanceExample() {
        // This is an example of a performance test case.
        measure {
            // Put the code you want to measure the time of here.
        }
    }

}

系统在创建该文件是已经帮我们创建了这个测试类,我们可以看一下这个类里面都有什么。

  1. 我们所有测试Case都继承自XCTestCase
  2. 里面包括两个方法:func setUp() func tearDown()
  3. setup()是在所有测试用例运行之前运行的函数,在这个测试用例里进行一些通用的初始化工作
  4. tearDown()是在所有的测试用例都执行完毕后执行的,主要做一些清理工作。
  5. func testExample()函数,一个测试用例里其中一个测试条目,这些测试条目必须以test作为函数的开头,才会被识别为一个测试条目。
  6. func testPerformanceExample()是用来测试性能的

测试代码的编写

普通测试

我们单元测试主要是针对工程内的一些代码逻辑及性能进行测试,所以我们把我们需要测试的模块及函数以接口的方式提供给测试工程;

例如,我们需要测试Caculator模块的func multiply(_ x: Int, y: Int) -> Int函数,测试代码如下:

import XCTest
import Caculator

class xxxTests: XCTestCase {

    override func setUp() {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }
    
     func testCaculator_mutilply() {
        let caculator = Caculator()
        let result = caculator.multiply(7, 8)
        XCTAssert("result", "结果出错")
    }

}

当我们编写测试代码之后,我们可以看到函数右侧有一个钻石符号,点击运行即可测试。

运行之后我们能看到运行结果

这里表示单元测试失败

我们也能在IDE看到哪一行代码失败

在运行过程中,发现错误我们还能够进行断点调试。

除此之外,我们还能够在Xcode左侧的导航栏看到我们整体的单元测试情况

我们还可以在Reporter Navigator中看到更多详细信息

性能测试

性能测试主要是用来评估一段代码运行的速度,在XCTest中,性能测试会对其中的代码运行10次,以此来保证测试结果的稳定。

下面是一段性能测试代码:

func testPerformanceExample() {
        // This is an example of a performance test case.
        measure {
            // Put the code you want to measure the time of here.
            for i in 0..<10000 {
                print("\(i)")
            }
        }
    }

我们把需要测试性能的代码放在measure{}的闭包里即可,运行测试步骤与普通逻辑测试一致。

但是,性能测试与普通逻辑测试不同的地方在于,性能测试需要一个评估指标,主要是根据这个指标来进行性能测试的;具体指标设置如下:

  1. 运行一次性能测试条目之后,则会在Xcode中出现函数总体运行的情况

  1. 这时我们点击左侧灰色按钮则会出现本次测试的总体情况

这里一共有几个指标:Metric:测试维度(这里默认是时间);Result:测试结果(这里没有设置基准线,所以没有测试结果);Average:平均运行时间;Baseline:基准线,是用来评估测试结果的一个重要指标,测试结果以基准线为准;Mac STDDEV:最大允许的标准差,用来苹果测试结果的另一个重要指标;

我们设置Baseline之后再进行一次测试;

这时我们看到,这次的测试结果要优于我们设置的Baseline,那么这次测试就通过了。

异步执行函数测试

由于我们实际项目中,存在比较多与服务器交互的情况,所以,XCTest中,异步函数测试也是一个重要部分。

  1. 定义一个或者多个XCTestExpectation,表示异步测试想要的结果;
  2. 设置超时时间timeout,表示异步测试函数执行的超时时间;
  3. 在异步的代码完成的最后,调用fullfill来通知异步测试满足条件。

常用的断言

XCTAssertTrue(expression, format...) // 为true时通过,为false时为format里的错误
XCTAssertFalse(expression, format...) // 为false时通过,为true时为format里的错误
XCTAssertEqual(expression1, expression2, format...) // 两个表达式值相等时通过,不等时format里的错误
XCTAssertNotEqual(expression1, expression2, format...) // 两个表达式值不相等时通过,相等时format里的错误
XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, format...) // 两个表达式值的差的绝对值在精度范围内时通过,不相等时format里的错误
XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, format...) // 两个表达式值的差的绝对值在精度范围外时通过,不相等时format里的错误
XCTAssertNil(expression, format...) // 表达式为nil时通过,不为nil时为format里的错误
XCTAssertNotNil(expression, format...) // 表达式不为nil时通过,为nil时为format里的错误

代码测试覆盖率

单元测试覆盖率也是一个非常有用的指标,在Xcode里面我们同样可以查看测试代码覆盖率,且步骤简单;

Edit Scheme -> Test -> Options -> Code Coverage -> ☑️

Disable/Enable Test Case

如果我们不想一些测试条目或者测试Target默认执行,我们还可以在Xcode里面把这些测试Case设置为disable;

方法一:

我们在Xcode左侧的导航栏里面右键选中我们想要disable的Target或者测试Case, 选择Disable xxx即可;

方法二:

也可以在Scheme设置里面进行修改

XCTest运行及测试报告

XCTest十分强大,如果能加入到持续集成的步骤里,则更能发挥其作用;如在某个分支打包前运行某些单元测试,并生成测试报告,则开发及测试人员能够及时获取到当前代码的稳定性。

执行单元测试

xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp -destination 'id=device_id'

使用xcodebuild即可在bash中执行单元

测试报告生成

当我们执行完单元测试后,会生成一个xxx.xcresult的文件,里面会反映我们本次测试情况,包括单元测试执行时间,测试通过情况等;我们也可以用xcpretty将测试报告导出;

xcpretty安装

$ gem install xcpretty

xcpretty使用

$ xcodebuild test -workspace MyApp.xcworkspace -scheme MyApp -destination 'id=device_id' | xcpretty --report html --output outputfolderdestination/report.html

输出样式:

XCTest补充

测试计划

在指定测试计划时,除了在scheme里面修改之外,还可以自己创建XCTestPlan,在CI集成测试流程中,只需要添加-testPlan myTestPlan即可,处理起来更加方便。

测试framework

如果需要测试自己创建的Framework里面的一些接口,也不需要对现有的代码进行修改,只需要在引入framework时,加上testable关键字进行声明即可:

@testable import MyFramework

除此之外,我们还需要把framework复制到我们项目中。

具体步骤如下:

  1. Under Build Phases, click on + icon to add new phase, and select New Copy Files Phase.

  2. Drag the newly created Copy Files phase above Compile Sources phase

  3. In the new Copy Files phase, select Frameworks for Destination drop down.

  4. Leave subpath blank. Let be default Copy only when installing.

  5. Under the table, click '+' and then select your Framework, i.e. VoiceSampler.framework

  6. Make sure Code Sign on Copy is checked (ticked).

具体参考链接:stackoverfolow