TestCleaner
It makes your tests cleaner.
Here is an existing XCTest that confirms that a Version
type’s ExpressibleByStringLiteral
is working by comparing the results to known good values:
// Before
func testExpressibleByStringLiteral() {
XCTAssertEqual("1", Version(major: 1)),
XCTAssertEqual("1.0", Version(major: 1)),
XCTAssertEqual("1.0.0", Version(major: 1)),
XCTAssertEqual("1.2", Version(major: 1, minor: 2)),
XCTAssertEqual("4.5.6", Version(major: 4, minor: 5, bugfix: 6)),
XCTAssertEqual("10.1.88", Version(major: 10, minor: 1, bugfix: 88)),
}
Using TestCleaner, we can clean this up a little without sacrificing Xcode’s ability to highlight lines containing failing tests. We also recommend using a local typealias
to help reduce line noise:
// After
func testExpressibleByStringLiteral() {
typealias V = Version
assertEqual(testCases: [
Pair("1", V(major: 1)),
Pair("1.0", V(major: 1)),
Pair("1.0.0", V(major: 1)),
Pair("1.2", V(major: 1, minor: 2)),
Pair("4.5.6", V(major: 4, minor: 5, bugfix: 6)),
Pair("10.1.88", V(major: 10, minor: 1, bugfix: 88)),
])
}
Now, the assert operation (“equal”) needs to be written in just one place, making the block of tests less error-prone and the intent clearer. It also reduces line length, although the typealias
is helping there.
Focus or Skip Tests
Borrowing syntax from Quick, you can focus any test or tests by adding an f
to the beginning, and only those tests will execute on the next run, allowing you to debug individual cases without having to haphazardly comment and uncomment lines:
func testExpressibleByStringLiteral() {
typealias V = Version
assertEqual(testCases: [
Pair("1", V(major: 1)),
fPair("1.0", V(major: 1)), // Only this test will run.
Pair("1.0.0", V(major: 1)),
]
}
You can also skip test cases by prepending them with x
:
func testExpressibleByStringLiteral() {
typealias V = Version
assertEqual(testCases: [
Pair("1", V(major: 1)),
xPair("1.0", V(major: 1)), // These last two tests will be ignored.
xPair("1.0.0", V(major: 1)),
]
}
These can be combined in a single test for added flexibility: use xPair
to skip some tests to start with, then focus in on just one to debug it with fPair
.
All Available Comparators
TestCleaner mirrors the full range of XCTAssert functions available:
- Boolean (true/false)
- LessThan
<
- GreaterThan
>
- LessThanOrEqual
<=
- GreaterThanOrEqual
>=
- Equal
==
- Equal (with floating-point accuracy)
==
- NotEqual
!=
- NotEqual (with floating-point accuracy)
!=
Custom Assertions
If you have a custom assertion function, you can use the assertCustom
function to use it with TestCleaner. Just make sure your custom assertion takes file: StaticString, line: UInt
parameters, and forward the ones that the tests
closure in assertCustom
passes to you.
assertCustom(
testCases: [
Pair(someLeftValue, someRightValue),
Pair(anotherLeftValue, anotherRightValue),
],
tests: { pair, file, line in
myCustomAssertion(
pair.left, pair.right,
file: file, line: line // <-- ⚠️ this is important!
)
try youCanAlsoThrowErrorsInHere() // They will also get attributed to the correct line.
}
)
When To Use TestCleaner
This tool is ideal for tests of pure transformations, where a certain input will always produce the same output. Examples include: parsing, mapping, converting, and calculating. It is not intended to be used for integration-style tests, where each assertion is preceded by many lines of setup.
Further Reading
TestCleaner was inspired by a blog post which was in turn inspired by a conversation with Brian King.