XCTests: What Are They and Why Should I Use Them?

Steve
8 min readAug 30, 2021

--

I’ve gotten this far without them. After all, they sound like a lot of extra code for something that I can do by running my app on Simulator and tapping around. Additionally, I just scrolled through this blog and found a dearth of colorful pictures. Yeah, it’s a No for me dawg.

Here. I added a picture. But if XCTests don’t excite you in grayscale, you don’t deserve XCTests in color.

An Intro

XCTest is a framework provided by Apple and Xcode that allows developers to automate testing in their projects. Tests can be written to verify that a project’s logic is working as expected, manipulate and check UI elements, and even run diagnostics on the performance of blocks of code.¹ Ultimately the point of a test is to “verify that a known, fixed input produces a known, fixed output.”² Developers write these self-contained custom test methods themselves and tailor them to the needs of their apps. To get started on doing exactly that, skip to my next post, XCTests: Actually Writing Them. If you’re not ready for that, or you’re not even sure you’re sold on going through the trouble of writing them in the first place, stick around and find out…

Why You Need ‘Em

Develop and test faster.

In test-driven development, developers write unit tests for individual features before actually implementing the feature.³ Obviously the test fails until the feature is completed, at which point it will succeed. As you build the feature though, you will want to run the app and see whether it works properly or not. This is pretty much the development process in a nutshell. Without unit tests written, this means you’ll have to boot up the app on a simulator or device, and likely tap through a few buttons and screens just to get to the feature you want to test. These tests cycles can often be the most time consuming aspect of development. With unit tests, however, you’ll be able to skip this rote process and let the computer run the test for you, more immediately telling you if the feature you built works the way you expected it to or not and shaving seconds off of each test cycle. When you’re running this cycle dozens of times a day, the time spend by automated unit tests can quickly add up to hours saved testing. A quicker building and testing process means a quicker development process.

Catch bugs earlier.

Automating the process makes it more practical to run all of your tests after every change in the app simply because you are less worried about the time it takes to test. If you are running tests every time you should be running them — before you create a pull request, before you deploy to a device, before you send an app to beta testing, and absolutely definitely always before you submit to the App Store⁴ —you’ll catch bugs earlier in the development process and they’ll be easier to fix. In other words, if all your tests were succeeding before you made a change or added a feature (including the tests not directly related to the change), but now one of the tests is failing, it’s a good hint that the change you just made is the culprit. It’s much easier to catch and fix the bug here and now than it will to have to come back and figure out which change it was that threw your app out of whack. The above is not a comprehensive list of when you should test (you can test whenever you want), but testing at these checkpoints is a good habit to get into.

Don’t get lazy or forget tests.

Automating the testing process also makes it easier to remember all the tests you ran when you were building other features of your app. You’ll write the test once while you’re building a feature and it will be catalogued. This way, when you click the button to run all your tests, you can be sure you are testing everything without forgetting about how a new dependency or feature might affect older features or other parts of the app in all the edge cases. You’ll never get lazy and say, “Ehhhh, that edge case never pops up… no need to test it,” or, “There’s no way the feature I’m building over here affects the feature I built months ago over there, I’m not going to worry about it.” Nor will you ever forget which tests you used to run to ensure a particular part of the app is working properly. Instead, you’ll automatically test all of it because Xcode is doing all the work, and it will all be recorded.

Find bugs on a molecular level.

Each unit test should each focus on its own minute aspect of the larger application. By only testing one thing at a time, there are fewer confounding variables. So when a given test fails, and it comes time to figure out with line of code contains your bug, you can more quickly and accurately narrow down the list of suspects.

Collaborate safely.

Of course, you know what you’re doing. You never make mistakes. But you’re not always the only person working on a code base. By way of quick example — suppose you wrote some code that ran a calculation for how much fuel a boat might need on a given trip offshore. To make for a slightly more pleasant user experience, you decided that you want the calculator to return the result rounded to the 10’s digit. So that the app will display “You’ll need about 130 gallons of fuel” rather than “You’ll need about 127.133482 gallons of fuel.” Definitely a better user experience… And your just-trying-to-help-but-it’s-becoming-meddlesome friend looked at your calculation and didn’t recognize the ceil(_:) function that you wisely used. You did this to always round up the result, thus ensuring your user never under-fills their tank and gets stranded at sea. Your friend, thinking he was doing you a favor by eliminating overly complicated code, changed the calculation to use the more familiar round(_:) function. This change will work fine in the 127.13 → 130 example above, but will round down to 120 gallons in any scenario where the result is between 120 and 125… thereby resulting in your user under-filling their tank. And now they’re stuck in a foggy shipping channel hoping the Coast Guard finds them before a narcoleptic captain at the helm of a container ship does. If you have written proper unit tests however, you’ll catch the change your “friend” made before it gets published and save your users a world of trouble.

Document how your app should behave.

If your fellow developer from the previous example had run the unit tests before pushing his changes to the main project, the test that 122.44, for example, rounds to 130, would have failed and prompted a closer look. He would see the test that basically translates, “if the calculation is working properly, a result of 122.44 should return 130.” The existence of such a test would have forced him to consider the fact that you had purposefully and deliberately designed the calculation to always round up. This sort of documentation is not only helpful to others working on your code, but can prove to be a lifesaver when you have to revisit your own old code.

Some Vocab Words In Context

In sort of the same way the smallest component of an element having the chemical properties of that element is the atom, XCTAssertion is the most basic part of unit testing. The line

XCTAssert(FuelCalculator.roundResult(122.44) == 130)

…will do the heavy lifting of testing whether the roundFuelResult method properly rounds 122.44 up to 130. If the roundFuelResult method returns anything other than 130, the test will fail.

A test method is a function that contains one or more test assertions (like XCTAssert). Test methods have no parameters, no return value, and always begin with the word test.⁵

func testFuelResultRounding() {
XCTAssert(FuelCalculator.roundResult(122.44) == 130)
}

…or

func testFuelResultRounding() {
XCTAssert(FuelCalculator.roundResult(122.44) == 130)
XCTAssert(FuelCalculator.roundResult(125) == 130)
XCTAssert(FuelCalculator.roundResult(127.133482) == 130)
}

…are examples of perfectly valid (but not necessarily optimal) test methods.

Test methods are always declared in a subclass of XCTestCase. You can use custom classes of type XCTestCase to declare a single test or, even better, to organize an entire test suite — a set of related tests:

You can have multiple test suites and subclasses of XCTestCase associated with a target, which “specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace. A target defines a single product; it organizes… [the source files] required to build that product.”⁶ It’s these behind-the-scenes instructions for building the product that allow you access to the setUp, tearDown and testExample methods you’ll use to run your tests.⁷ (Don’t worry, you aren’t responsible for the instructions — Xcode handles all that. I’m just giving you a peak under the hood.) In most basic cases, your project will be synonymous with your target.

The Different Types of Tests

The three main types of testing are: functional tests, user interface tests, and performance tests. This introduction focused on the most common type of functional testing — unit testing.

What makes any given test a “unit test” is the idea that the test’s focus — the “System Under Test” (SUT) — is a very small piece of the larger program. Apple defines a “unit” of code as, “the smallest testable component of you project — for example, a method in a class or a set of methods that accomplish an essential purpose.”⁷ In general, this type of functional test is what you will use for testing your Model components (…and maybe some parts of your Controller components, and even View components if you really want).

And while you can run functional unit tests on View components, more commonly, user interface tests will be your weapon of choice there. UI tests differ from functional and performance tests in that they are recordings you generate while you use your app, rather than written code. As their name suggests, they allow you to check that elements of your UI are working properly (e.g. a given UILabel is hidden or visible or a UITableViewCell is populating with the correct title text).

Finally, performance tests. These tests allow you to “gather metrics while running your code, and report a failure if the metrics become significantly worse than a baseline value.”⁸ The baseline is a value that you specify as the developer. This way you can measure how long your app takes to perform a given task on different devices.

Next Steps

This article is meant to serve as an introduction to the XCTest framework. To get started writing your own tests, go ahead and check out my next post, XCTests: Actually Writing Them. It mostly focuses on functional unit tests.

If you’re looking for even more information on tests, I’d encourage you to visit the pages cited in the footnotes below. For a quick walk-through on recording your first UI test, this section of a raywenderlich.com tutorial should be able to get you started. And similarly the next section of the same tutorial will get you started with performance testing.

[1] Apple Inc. XCTest. Apple Developer Swift Documentation. https://developer.apple.com/documentation/xctest. (Accessed 2021, August 29)

[2] Bulavin, V. (2019, August 13). Real-World Unit Testing in Swift. Yet Another Swift Blog. https://www.vadimbulavin.com/real-world-unit-testing-in-swift/.

[3] Microsoft. Unit Testing: Apply Test-Driven Development to your Database Projects. Microsoft Development Network Documentation. https://docs.microsoft.com/en-us/archive/msdn-magazine/2008/launch/unit-testing-apply-test-driven-development-to-your-database-projects. (Accessed 2021, August 29)

[4] Wahlbeck, Mark (2018). Lecture 52: Unit Testing Our Data [MOOC lecture]. In M. Wahlbeck, iOS 11 & Swift 4: From Beginner to Paid Professional. Udemy. https://www.udemy.com/course/devslopes-ios11/learn/lecture/7407534

[5] Apple Inc. Defining Test Cases and Test Methods. Apple Developer Swift Documentation. https://developer.apple.com/documentation/xctest/xctestcase/understanding_setup_and_teardown_for_test_methods. (Accessed 2021, August 23)

[6] Apple Inc. Xcode Concepts: Xcode Target. Apple Developer Documentation Archive. https://developer.apple.com/library/archive/featuredarticles/XcodeConcepts/Concept-Targets.html. (Accessed 2021, August 29)

[7] Apple Inc. Xcode Overview: Using Unit Tests. Apple Developer Documentation Archive. https://developer.apple.com/library/archive/documentation/ToolsLanguages/Conceptual/Xcode_Overview/UnitTesting.html. (Accessed 2021, August 29)

[8] Apple Inc. Performance Tests. Apple Developer Swift Documentation. https://developer.apple.com/documentation/xctest/performance_tests. (Accessed 2021, August 29)

--

--