I rather like @DariusV's solution. However, it doesn't handle well when I the developer execute the test method directly from Xcode's sidebar. That's a dealbreaker for me.
What I wound up doing I think is rather slick.
I declare a Dictionary
of testValues
(probs need a better name for that) as an instance computed property of my XCTestCase
subclass. Then, I define a Dictionary
literal of inputs keying expected outputs. My example tests a function that acts on Int
, so I define testValues
like so:
static var testRange: ClosedRange<Int> { 0...100 }
var testValues: [Int: Int] {
let range = Self.testRange
return [
// Lower extreme
Int.min: range.lowerBound,
// Lower bound
range.lowerBound - 1: range.lowerBound,
range.lowerBound : range.lowerBound,
range.lowerBound + 1: range.lowerBound + 1,
// Middle
25: 25,
50: 50,
75: 75,
// Upper bound
range.upperBound - 1: range.upperBound - 1,
range.upperBound : range.upperBound,
range.upperBound + 1: range.upperBound,
// Upper extreme
Int.max: range.upperBound
]
}
Here, I very easily declare my edge and boundary cases. A more semantic way of accomplishing the same might be to use an array of tuples, but Swift's dictionary literal syntax is thin enough, and I know what this does.
Now, in my test method, I have a simple for
loop.
/// The property wrapper I'm testing. This could be anything, but this works for example.
@Clamped(testRange) var number = 50
func testClamping() {
let initial = number
for (input, expected) in testValues {
// Call the code I'm testing. (Computed properties act on assignment)
number = input
XCTAssertEqual(number, expected, "{number = \(input)}: `number` should be \(expected)")
// Reset after each iteration.
number = initial
}
}
Now to run for each parameter, I simply invoke XCTests in any of Xcode's normal ways, or any other way that works with Linux (I assume). No need to run every test class to get this one's parameterizedness.
Isn't that pretty? I just covered every boundary value and equivalence class in only a few lines of DRY code!
As for identifying failing cases, each invocation runs through an XCTAssert
function, which as per Xcode's convention will only throw a message at you if there's something wrong that you need to think about. These show up in the sidebar, but similar messages tend to blend together. My message string here identifies the failing input and its resulting output, fixing the blending-togetherness, and making my testing flow a sane piece of cherry apple pie. (You may format yours any way you like, bumpkin! Whatever blesses your sanity.)
Delicious.
TL;DR
An adaptation of @Code Different's answer: Use a dictionary of inputs and outputs, and run with a for
loop.