Now we’re reaching the critical goal of this chapter: avoiding the testing mistake most Swift programmers make. Let’s apply a common Swift programmer approach to duplication. We’ll see how it creates problems for test code. Delete the XCTFail assertion in test_methodOne. This brings the MyClassTests suite back to this:
| class MyClassTests: XCTestCase { |
| |
| func test_methodOne() { |
| let sut = MyClass() |
| |
| sut.methodOne() |
| |
| // Normally, assert something |
| } |
| |
| func test_methodTwo() { |
| let sut = MyClass() |
| |
| sut.methodTwo() |
| |
| // Normally, assert something |
| } |
| |
| } |
See how each test starts with the same line, creating an instance of MyClass? A typical Swift programmer would think, “Why not promote sut from local variables to a property?” It would look like this:
| class MyClassTests: XCTestCase { |
| private let sut = MyClass() |
| |
| func test_methodOne() { |
| sut.methodOne() |
| |
| // Normally, assert something |
| } |
| |
| func test_methodTwo() { |
| sut.methodTwo() |
| |
| // Normally, assert something |
| } |
| } |
Everything looks cleaner, and more “Swifty.” So what’s the problem? Run the tests and look at the test transcript. If you drill down to the “All tests” level, you’ll see something like this:
| >> MyClass.init() #1 |
| >> MyClass.init() #2 |
| Test Suite 'All tests' started at 2019-06-22 20:12:15.128 |
| Test Suite 'LifeCycleTests.xctest' started at 2019-06-22 20:12:15.128 |
| Test Suite 'MyClassTests' started at 2019-06-22 20:12:15.129 |
| Test Case '-[LifeCycleTests.MyClassTests test_methodOne]' started. |
| >> methodOne |
| Test Case '-[LifeCycleTests.MyClassTests test_methodOne]' passed |
| (0.001 seconds). |
| Test Case '-[LifeCycleTests.MyClassTests test_methodTwo]' started. |
| >> methodTwo |
| Test Case '-[LifeCycleTests.MyClassTests test_methodTwo]' passed |
| (0.000 seconds). |
| Test Suite 'MyClassTests' passed at 2019-06-22 20:12:15.131. |
| Executed 2 tests, with 0 failures (0 unexpected) in 0.001 (0.002) |
| seconds |
| Test Suite 'LifeCycleTests.xctest' passed at 2019-06-22 20:12:15.131. |
| Executed 2 tests, with 0 failures (0 unexpected) in 0.001 (0.003) |
| seconds |
| Test Suite 'All tests' passed at 2019-06-22 20:12:15.131. |
| Executed 2 tests, with 0 failures (0 unexpected) in 0.001 (0.004) |
| seconds |
Look for the logging of the init and deinit calls.
Two instances of MyClass are created before tests are even run. And they’re never destroyed.
This is no longer the clean room we want for our tests. Problems can potentially multiply:
If there’s a problem creating MyClass, it will happen before any tests run.
If there’s a problem destroying it, we’ll never know because the instances aren’t destroyed.
If instantiating MyClass has any global side effects, such as swizzling methods or touching the file system, all bets are off.
What is going on? Keep reading.
![]() |
The problems that come with “legacy” code don’t apply to only production code. You can have “legacy” test code as well. Like any code, test code requires ongoing care to keep it in shape. Any tests that fail to preserve the clean room create potential problems. These problems can go unnoticed for a long time. So make sure to review the quality of test code, old and new, with the same rigor you apply to production code. |