admin管理员组

文章数量:1122826

I use the Swift Testing framework in my iOS project. Imagine I have a function I need to test:

func isEven(_ num: Int) -> Bool

I’m working with parameterized tests, where multiple test cases are evaluated using the same test function. I’d like Xcode to show the exact line of the failing test case, similar to how Issue.record works.

Here’s my current setup:

@Test(arguments: testCases)
func test() {
    let output = isEven(testCase.input)
    #expect(output == testCase.expectedOutput) // I need to somehow pass file and line here
}

static var testCases: [TestCase<Int, Bool>] = [
    (2, true),
    (0, true),
    (3, false),
    (5, true) // Incorrect expected value for demonstration
]

struct TestCase<Input, Output> {
    public let input: Input
    public let expectedOutput: Output
    public let file: StaticString
    public let line: UInt

    public init(input: Input, expectedOutput: Output, file: StaticString = #file, line: UInt = #line) {
        self.input = input
        self.expectedOutput = expectedOutput
        self.file = file
        self.line = line
    }
}

What I’m Trying to Achieve

  • Xcode to report the exact file and line where the test case failed (In case of parametrised tests).

Question

  • Is it possible to pass file and line to #expect in the Swift Testing framework?
  • If not, is there an alternative way to achieve the desired behavior? Any guidance would be appreciated!

I use the Swift Testing framework in my iOS project. Imagine I have a function I need to test:

func isEven(_ num: Int) -> Bool

I’m working with parameterized tests, where multiple test cases are evaluated using the same test function. I’d like Xcode to show the exact line of the failing test case, similar to how Issue.record works.

Here’s my current setup:

@Test(arguments: testCases)
func test() {
    let output = isEven(testCase.input)
    #expect(output == testCase.expectedOutput) // I need to somehow pass file and line here
}

static var testCases: [TestCase<Int, Bool>] = [
    (2, true),
    (0, true),
    (3, false),
    (5, true) // Incorrect expected value for demonstration
]

struct TestCase<Input, Output> {
    public let input: Input
    public let expectedOutput: Output
    public let file: StaticString
    public let line: UInt

    public init(input: Input, expectedOutput: Output, file: StaticString = #file, line: UInt = #line) {
        self.input = input
        self.expectedOutput = expectedOutput
        self.file = file
        self.line = line
    }
}

What I’m Trying to Achieve

  • Xcode to report the exact file and line where the test case failed (In case of parametrised tests).

Question

  • Is it possible to pass file and line to #expect in the Swift Testing framework?
  • If not, is there an alternative way to achieve the desired behavior? Any guidance would be appreciated!
Share Improve this question asked Nov 21, 2024 at 15:03 Levan KaranadzeLevan Karanadze 4412 gold badges16 silver badges27 bronze badges 2
  • Just to be clear, you want the line (5, true) to be highlighted? – Sweeper Commented Nov 21, 2024 at 15:13
  • Yeah, exactly. In this case I want the line (5, true) to be highlighted. – Levan Karanadze Commented Nov 22, 2024 at 5:26
Add a comment  | 

2 Answers 2

Reset to default 4

#expect takes a sourceLocation: parameter, which defaults to the location of the #expect macro. You can replace it with your own SourceLocation.

Your idea of taking in the line number in the initialiser of TestCase is in the right direction. Instead of the line number, take a SourceLocation.

@Test(arguments: testCases)
func test(testCase: TestCase<Int, Bool>) {
    let output = testCase.input.isMultiple(of: 2)
    #expect(output == testCase.expectedOutput, sourceLocation: testCase.sourceLocation)
}

let testCases: [TestCase<Int, Bool>] = [
    .init(input: 1, expectedOutput: true), // Xcode will highlight this line
    .init(input: 2, expectedOutput: true),
    .init(input: 3, expectedOutput: false),
]

public struct TestCase<Input: Sendable, Output: Sendable>: Sendable {
    public let input: Input
    public let expectedOutput: Output
    public let sourceLocation: SourceLocation

    public init(input: Input, expectedOutput: Output, sourceLocation: SourceLocation = #_sourceLocation) {
        self.input = input
        self.expectedOutput = expectedOutput
        self.sourceLocation = sourceLocation
    }
}

Note the use of the #_sourceLocation macro to get the current source location. If you don't like seeing underscore APIs for some reason, you can always take the 4 components of a SourceLocation as parameter instead:

public init(input: Input, expectedOutput: Output, fileID: String = #fileID, filePath: String = #filePath, line: Int = #line, column: Int = #column) {
    self.input = input
    self.expectedOutput = expectedOutput
    self.sourceLocation = SourceLocation(fileID: fileID, filePath: filePath, line: line, column: column)
}

The #expect can accept a SourceLocation:

func foo(value: Int, _ sourceLocation: SourceLocation = #_sourceLocation) {
    #expect(value == 42, sourceLocation: sourceLocation)
}

@Test
func bar() {
    foo(value: 1)           // Shows up here: Expectation failed: (value → 1) == 42
}

本文标签: iosCan I Pass file and line to expect in Swift TestingStack Overflow