Let’s start with the basics
Unit testing is one of those programming topics that can often seem much more complicated and difficult than what it actually is. Sure, there’s definitely a substantial amount of depth and variety as to what kind of tests that we can write, how we can run them, what frameworks and tools that are being used, and so on — but there are often a number of ways that we can build up a solid test coverage for a given project incrementally, over time.
On this Discover page, you’ll find links to lots of articles and tips that should either help you get started with unit testing completely from scratch, or provide you with a boost of inspiration as to how you could make an existing testing suite easier to run, update and maintain.
Making existing code testable
One of the things that does make unit testing slightly more complex than other testing variants is that making full use of it often requires us to write our code in somewhat specific ways.
However, even if a given code base wasn’t written with testing in mind, there are often ways that it can be tweaked in order to make it much more testable. These articles contains tips and tricks on how to make such tweaks in various situations:
To mock or not to mock
Mocking is definitely a very core technique when it comes to unit testing, and can often enable us to take control over our dependencies in a way that makes them completely predictable and verifiable within our tests.
However, heavy use of mocking can also make a given code base much more complex, so striking a balance between testability and complexity often becomes key. Here are a few articles that explore that topic, both by taking a look at various mocking techniques, and when it might be a better idea to not use any mocks at all:
Testing asynchronous logic
Asynchronous code tends to be particularly tricky to test, given that a unit test is always executed synchronously from top to bottom, which typically doesn’t give our asynchronous operations enough time to finish.
XCTest framework includes a number of tools and APIs that can be used to await the results of such asynchronous code, which we can then augment with our own tools and utilities as well:
Handling failures and unexpected conditions
Each test that we add to a code base is likely to start failing at some point. After all, the whole purpose of investing time in automated testing is to be able to detect regressions and other bugs early in the development process, while there’s still a chance for us to fix them before they reach our users.
Because of that, it’s definitely worth spending a little bit more time upfront on ensuring that any failures that our tests will encounter will be as easy to identify and debug as possible. Here are some ways to do just that:
Structure and maintenance
Making error messages as clear as possible definitely goes a long way towards making a test suite easier to maintain, but there are also a number of other things that we can do to make the structure and execution of our tests much more predictable.
From combating “flakiness”, to enabling certain opt-in Xcode features that can help us speed up our tests, to defining robust testing data — here are my top tips around test structure and maintenance:
The road to a solid test coverage
While achieving a 100% unit testing coverage isn’t always a good goal to have (since, at some point, we’ll start to get diminishing returns), with the right kind of strategies in place, we can continuously build up a given project’s test coverage as we keep working on it. Here are a few such strategies that I recommend exploring: