You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XCTest参数化测试重试时变量为nil的问题解决咨询

解决XCTest参数化测试重试时变量为nil的问题

问题描述

我通过重写defaultTestSuite实现参数化UI测试,故意让值为2的用例失败。配置测试计划重试失败用例后,重试该用例时self.number变为nil,导致测试因空指针错误失败。如何避免这种情况?

原测试代码

import XCTest

final class HelloiOSUITests: XCTestCase {
    var number: Int?
    
    override func setUpWithError() throws {
        continueAfterFailure = false
    }

    func testExample() throws {
        guard let num = self.number else {
            XCTFail("Failed with NPE")
            throw NSError()
        }
        print("Running for number: \(num)")
        if num == 2 {
            XCTFail("Failing on purpose to demonstrate NPE issue.")
        }
    }
    
    override class var defaultTestSuite: XCTestSuite {
        get {
            let suite = XCTestSuite(name: NSStringFromClass(self))
            addTestCase(to: suite, value: 1)
            addTestCase(to: suite, value: 2)
            addTestCase(to: suite, value: 3)
            print("Test method count = \(suite.testCaseCount)")
            return suite
        }
    }
    
    static func addTestCase(to suite: XCTestSuite, value: Int) {
        for invocation in self.testInvocations {
            let testMethod = HelloiOSUITests(invocation: invocation)
            testMethod.number = value
            suite.addTest(testMethod)
        }
    }
}

问题原因

XCTest在重试失败用例时,会重新创建一个新的HelloiOSUITests实例,但这个新实例不会继承原实例的number属性值——原代码仅在初始创建测试实例时设置number,重试时新实例的number未被赋值,因此变为nil。

解决方法

将测试参数与NSInvocation绑定,利用其userInfo存储参数值,在测试实例的setUp阶段从invocation中取出参数并赋值给number。这样无论重试多少次,新实例都能从关联的invocation中获取正确参数。

修改后的代码

import XCTest

final class HelloiOSUITests: XCTestCase {
    var number: Int?
    
    override func setUpWithError() throws {
        continueAfterFailure = false
        // 从invocation的userInfo中取出参数赋值给number
        if let value = invocation.userInfo?["number"] as? Int {
            self.number = value
        }
    }

    func testExample() throws {
        guard let num = self.number else {
            XCTFail("Failed with NPE")
            throw NSError(domain: "HelloiOSUITests", code: -1)
        }
        print("Running for number: \(num)")
        if num == 2 {
            XCTFail("Failing on purpose to demonstrate NPE issue.")
        }
    }
    
    override class var defaultTestSuite: XCTestSuite {
        get {
            let suite = XCTestSuite(name: NSStringFromClass(self))
            addTestCase(to: suite, value: 1)
            addTestCase(to: suite, value: 2)
            addTestCase(to: suite, value: 3)
            print("Test method count = \(suite.testCaseCount)")
            return suite
        }
    }
    
    static func addTestCase(to suite: XCTestSuite, value: Int) {
        for invocation in self.testInvocations {
            // 复制invocation,避免修改原实例影响其他测试用例
            let copiedInvocation = invocation.copy() as! NSInvocation
            // 将参数存入invocation的userInfo
            copiedInvocation.userInfo = ["number": value]
            let testMethod = HelloiOSUITests(invocation: copiedInvocation)
            suite.addTest(testMethod)
        }
    }
}

关键修改点

  • 在addTestCase中,复制原invocation并将参数存入其userInfo,避免修改原invocation导致参数污染
  • 在setUpWithError中,从当前实例的invocation中取出userInfo里的参数,赋值给number
  • 确保重试时新创建的测试实例能通过关联的invocation获取到正确的参数值

内容的提问来源于stack exchange,提问作者coldcake71

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 05:22:51