How To Win Over a Unit Testing Sceptic

01 Jun 2012 8 min read

Convincing a developer to write unit tests - especially if they've never done so before - can feel impossible. The pushback is often visceral. Like Sisyphus endlessly pushing that boulder uphill, you're met with the same objection every time:

“Writing unit tests will only slow me down. The user won't get the feature they need.”

It's a seductive argument. After all, speed and delivery are the lifeblood of software development. But what if that objection rests on a false premise? What if unit testing doesn't slow you down - but actually speeds you up? What if it sharpens your design decisions and safeguards your future velocity, allowing future-you to ship even more features with greater confidence?

Painting of Sisyphus struggling to push a boulder uphill

In this post, I want to challenge that objection head-on. I'll walk you through the tangible benefits of unit testing-how it helps you catch bugs early, refactor with confidence, design better APIs, and ultimately ship more reliable software. Whether you're a sceptic or a seasoned advocate, I hope this post gives you new language, examples, and motivation to make unit testing a core part of your development practice.

Before exploring the benefits of unit testing, let's ensure we have a clear understanding of what unit testing entails.

What is Unit Testing?

Unit testing ensures that, given a particular app state, a specific and isolated part of your app behaves as expected. A unit can be anything, but ideally, it's as small as possible. In iOS, a method is typically considered a unit. Each test acts as a strict, written contract that the unit must satisfy for the test to pass. If the unit contains multiple paths (e.g., if and else branches), multiple unit tests are needed to ensure complete coverage.

Unit tests are grouped into a test suite - known as a regression suite - within a dedicated test target.

Each unit test should execute in isolation, unaffected by the state of other tests. It's the test's responsibility to ensure that any required conditions are met - such as providing test data, creating test doubles, accepting user permissions, and so on. Once the test has run, it must clean up any shared resources to avoid impacting other tests in the suite. This self-contained approach strictly controls the environment in which the unit test is run, ensuring that tests are repeatable and reliable.

For more details on unit testing as a process, check out this post: How Unit Tests Protect Creativity and Speed Up Development.

What are the benefits of Unit Testing?

Velocity & Efficiency

These benefits show how unit testing improves development speed and reduces manual effort.

1. Swap Human Effort for Automated Insight

Unit tests run at the speed of your CPU - far faster than any manual tester could execute the same checks. Depending on the size of your test suite, you can run it dozens of times a day. Each run gives you rapid feedback on whether your latest changes have broken existing behaviour.

Think of unit tests as a turbocharged safety net: lightweight, fast, and always ready to catch you before you fall. They provide rapid feedback during development, helping you identify issues early and fix them quickly. They don't just protect your code - they protect your momentum. With them in place, you spend less time debugging and more time building.

This doesn't mean manual testing disappears. But with a solid unit test foundation, every manual pass becomes more focused, more meaningful, and less likely to uncover surprises. The result? Faster feedback, fewer regressions, and a smoother path to shipping.

2. Pinpoint Failures in Seconds

Unit tests offer feedback in two key moments:

  • During the writing of the test, when you clarify expectations.
  • During the running of the test, when you validate behaviour.

When a test fails, you know precisely which one failed, what the state of the system was beforehand, and what the test was trying to assert. There's no need to sift through logs or decipher vague bug reports. This kind of immediate, localised feedback lets you skip the detective work and go straight to the fix - saving time, reducing frustration, and keeping your momentum intact.

Fast feedback isn't just a convenience - it's a catalyst for getting features into your users' hands.

Quality & Reliability

These benefits focus on catching bugs, meeting expectations, and ensuring long-term correctness.

3. Catch Bugs Early

Unit tests catches bugs before they ship. By asserting expected behaviour during development, tests act as a first line of defence. When reality diverges from expectation, the test fails - giving you an immediate, precise signal that something's off. You trace the failure, apply the fix, and rerun the test to confirm the correction.

This fast feedback loop doesn't just protect your code - it protects your momentum by ensuring that bugs are caught when fixing them is cheapest.

4. Verify the Spec

Unit tests should validate what the unit under test is meant to do, not just confirm what it currently does. That distinction is crucial. When we test against the specification, we anchor our expectations in intent, not accident. We're not just rubber-stamping the implementation - we're enforcing its contract.

Without this, we risk shipping features that claim to behave one way but act another.

5. Guard Against Regression

Unit tests form a suite of regression checks that can be run whenever changes are made to the app. Having a regression suite is especially valuable when the original developers are no longer involved - unit tests preserve intent long after the code was written. If a bug escapes into the wild, the fix begins with writing a failing test that captures the expected behaviour. Once the bug has been squashed, the test becomes a permanent safeguard - ensuring that the same bug never gets out again.

Regression tests don't just protect the past; they also protect the future. They encode lessons learned, turning every fix into a future-proof promise.

6. Build Trust Through Passing Tests

Few things sting more than hearing that something you built doesn't work - especially when it's already in the hands of users. Unit tests help catch bugs early, during development, when fixes are faster, cheaper, and less public. They act as a quiet safety net, protecting not just your code, but your confidence, your reputation, and the trust others place in your team to deliver.

Passing tests aren't just technical signals - they reassure you that the app behaves as expected, that your changes haven't broken anything, and that your team can move forward with clarity and confidence.

Design & Architecture

These benefits highlight how unit testing encourages better code structure and modularity.

7. Refactor Toward Testability

Unit tests reward code that's small, independent, and testable in isolation - qualities that also define good design. To write effective tests, your project needs to be organised into discrete, focused units that don't rely on hidden dependencies or shared state.

Let's look at a simple before-and-after example:

- (NSString *)formatDownloadStatus 
{
  return [NSString stringWithFormat:@"Download Status: %@", [Downloader.shared downloadStatus]];
}

This method reaches into a singleton to retrieve the downloadStatus, then formats it into a string. While functional, it's tightly coupled to the Downloader class, making it hard to test in isolation.

Now consider a refactored version using dependency injection:

- (NSString *)formatDownloadStatusFor:(NSString *)downloadStatus 
{
  return [NSString stringWithFormat:@"Download Status: %@", downloadStatus];
}

Here, the downloadStatus is passed in directly, a move from implicit dependency to explicit injection - a hallmark of testable, modular design. The method no longer even needs to know that Downloader exists, and testing becomes trivial with no need for a test-double to be involved.

In practice, you'll often find that if a method is hard to unit test, it's often a signal that its design can be improved. Testing doesn't just validate behaviour - it reveals architectural friction and guides you to cleaner, more maintainable code.

8. Change the 'How' Without Breaking the 'What'

The secret superpower of unit testing is in the confidence it provides during refactoring. With a robust test suite in place, you can reshape existing implementations without fear of introducing regressions. Without tests, refactoring becomes a cautious, manual process - often relying on UI checks that are slow, error-prone, and often incomplete. It's especially hard to cover all the unhappy paths - the edge cases, failure modes, and subtle interactions that slip through visual inspection.

Unit tests shift that burden. They verify that behaviour remains consistent even as the underlying code evolves, allowing you to change the how without breaking the what.

9. Isolate Components Before You Integrate

Unit tests gives us confidence that individual components behave correctly in isolation. So when we integrate those components, any issues that arise are likely due to the integration itself - not the internal workings of each part. This dramatically narrows the search space when tracking down bugs, making diagnosis faster and more focused.

Isolation isn't just a testing strategy - it's a design principle. When components are independently verifiable, they become easier to reason about, easier to reuse, and easier to trust.

Communication & Culture

These benefits support team collaboration, onboarding, and disciplined development.

10. Executable Documentation That Never Lies

A well-maintained suite of unit tests becomes executable, living documentation - one that evolves with the code and reflects its true behaviour. For new developers joining the project, these tests offer a practical, executable guide to how the app works. They illuminate not just the happy paths, but also the unhappy paths.

Because unit tests are tightly coupled to the codebase, they're more likely to stay up to date than traditional documentation, which can drift out of sync without consequence. When the code changes, the tests must change too-or they fail. This built-in accountability makes unit tests a more reliable source of truth than a stale wiki page or forgotten README. They don't just describe the system - they verify it.

11. Guard Your Codebase Against Bloat

Unit tests act as a natural checkpoint against unnecessary features. Every new feature must be tested - and writing those tests takes time. That friction isn't a flaw; it's a filter. It forces you to ask: Is this feature truly worth the effort to validate and maintain? In this way, unit testing discourages speculative additions and helps keep the codebase focused.

After all, if there's one thing developers hate more than bugs, it's doing unnecessary work.

Sceptics still holding out on you?

As developers, we respond best to seeing things in action. So if you've still got sceptics on your team, it's time to show - not tell. Start small: pick a method with a few if and else branches, and write a single test for the happy path. No test doubles. No complex setup. Just verify that for a given input, you get the expected output.

That first passing test often converts sceptics faster than any argument. It's tangible proof that unit testing doesn't have to be painful.

So is it worth the struggle?

Convincing someone to start unit testing may feel like pushing that boulder - heavy, uphill, and resistant to momentum. But unlike Sisyphus, you're not cursed to push forever. Each passing test, each bug caught, each design refined flattens the hill and quietens the pushback. Over time, the struggle turns into momentum. With unit tests, you're not just shipping faster - you're shipping smarter.

So the next time you hear “unit tests will slow me down,” remember: speed without stability is just chaos in motion.

For a walkthrough on how to write unit tests, check out this post: A Unit Testing Walkthrough.

What do you think? Let me know by getting in touch on Mastodon or Bluesky.