This is not the first article I have done on parameterised tests, but it is my first article on Swift.

Recently I have been working on an iOS app and I couldn't find any good solutions for data-driven tests. Some of the solutions I found were language hacks to try and trick the test suite into thinking there were more tests than were really there.

I've come up with a simple generator based approach that (in it's more simple form) looks like this:

class StepUnitFormatterTests: XCTestCase {
//> (0, "0 steps")
//> (1, "1 step")
//> (2, "2 steps")
//> (999, "999 steps")
//> (1000, "1,000 steps")
//> (1000000, "1,000,000 steps")
//> (3.3, "3 steps")
//> (-5, "0 steps")
func testFormat(_ numberOfSteps: Double, _ expected: String) {
XCTAssertEqual(StepUnitFormatter().format(numberOfSteps), expected)
}
}

Each of the lines that are prefixed with //> describe a single data-driven test, and they apply to whatever the next Swift function is. testFormat in this case.

In step the test generator. The magic comes from generate_tests.py. It reads the comments and translates it into actual Swift before the compilation begins. It does this by either adding or updating an extension at the end of the same test file. There is an example here of what that looks like.



All you have to do to get this working is to download generate_tests.py and place it in your project directory. Then add a Run Script phase like the screenshot on the left. Make sure you add this to your Test target(s) and not to the application target.



generate_tests.py will only parse the files and folders provided as arguments, so change the arguments to where your test directory is (relative the project root). You can provide as many directories or single files as you need. And you will see the output of all the files parsed in the build log.

More Readable Tests

The Swift tests generated will be numbered. From the example above you will get testFormat0, testFormat1, etc, for each test. If you prefer to control the name of the tests you can specify a name before the function arguments like:

class StepUnitFormatterTests: XCTestCase {
//> Zero(0, "0 steps")
//> One(1, "1 step")
//> (999, "999 steps")
func testFormat(_ numberOfSteps: Double, _ expected: String) {
XCTAssertEqual(StepUnitFormatter().format(numberOfSteps), expected)
}
}

This will generate tests called testFormatZero, testFormatOne and testFormat2 respectively.

It is also worth noting that the line is pure swift so you can provide any expression and/or named arguments if you feel it makes the data more readable:

class StepUnitFormatterTests: XCTestCase {
//> Minute(seconds: 60)
//> Hour(seconds: 60 * 60)
func testFormat(seconds: Int) {
// ...
}
}


comments powered by Disqus