Home
Tags Projects About
Ode to Unit Tests

Ode to Unit Tests

The Law of Testing: The degree to which you know how your software behaves is the degree to which you have accurately tested it.— Code Simplicity by Max Kanat-Alexander

To verify that applications perform correctly, developers use various types of tests. Some focus on individual function logic, while others cover all system layers, from the UI down to databases and external services.

Certain test types operate in between, calling the application API while using stubs for external services. The elements tested dictate the testing scope: fewer parts mean a smaller scope, while testing more substantial parts indicates a larger scope.

Broader scope tests consume more computational resources and take longer to execute, but they validate larger functionality sections. Additionally, tests with larger scopes come with longer feedback loops and higher maintenance overhead.

Today, we’re diving into Unit Tests, which sit at the base of the testing pyramid and offer the shortest feedback cycles.

Unit Tests

Tweet

So, what’s a Unit Test? It’s code that verifies that another piece of code works as expected. Key characteristics of a Unit Test include the following:

  • It validates functionality of isolated parts — the units — typically classes or functions
  • It’s written by developers as they work on code
  • It runs easily, without needing extra setup
  • It executes quickly
  • It integrates smoothly with Continuous Integration (CI), as it requires only the code itself

Good Unit Tests follow the F.I.R.S.T. principles:

  • Fast — Tests must be fast. If they’re slow, developers hesitate to run them frequently. Skipping tests leads to late problem discovery, preventing easy fixes and safe refactoring.

  • Independent (or Isolated) — Tests should not depend on each other. Developers need to be able to run tests in any order (ideally in parallel). Dependencies create cascades of failures that obscure root causes, complicating troubleshooting. The Single Responsibility Principle (SRP) SOLID Principles states that classes should be small and single-purpose. This can be applied to your tests as well. If one of your tests may fail for several reasons, consider splitting it into separate tests.

  • Repeatable — Tests should be repeatable in any environment and must produce consistent results, whether in production, staging, or on a developer’s machine. Tests failing intermittently are just as problematic as code bugs themselves.

  • Self-validating — Each test should determine whether it passed or failed. Results should be clear-cut: either pass (✅) or fail (❌). If tests aren’t self-validating, they risk becoming subjective, requiring manual evaluation and log inspection.

  • Timely — Tests should be written alongside feature implementation. Writing tests after-the-fact often requires refactoring working code, adding effort, and increasing the risk of missed edge cases.

Why Testing?

With the "what" in place, the next question is why? The obvious answer: Unit Tests help verify code logic and detect defects. But more importantly, they keep the application stable, allowing developers to add features and fix issues faster.

Effective Unit Tests support three main pillars of maintenance: understand, modify, and test:

  • Properly designed Unit Tests help clarify the application’s functionality.
  • They quickly signal if new code affects existing functionality.
  • They encourage a more modular, testable code structure, which typically leads to better code quality.

A common argument against Unit Testing is the claim that it slows development, especially early in a project when initial capabilities are rapidly built out. However, as a project evolves, you’ll find yourself rewriting old code frequently. Time goes into understanding the old code and ensuring changes don’t break it. This is where Unit Tests shine. Application maintenance time vastly exceeds initial development time — the longer-term benefits of Unit Testing often outweigh the initial investment.

Write unit tests; you’ll thank yourself later.

Code Coverage

Code coverage measures the extent to which a program’s source code is tested. Typically, a program with high code coverage was tested more thoroughly and has fewer chances to contain errors than a program with low code coverage. But you cannot rely entirely on this metric, because even if 100% of the code is covered, we still know nothing about the quality of tests. Some tests may cover the code, but they are useless, have no necessary statements, or are just badly designed. It sounds counter-intuitive, but knowing which code is covered is often more helpful than simply aiming for high coverage.

So, treat code coverage as a tool for identifying areas needing additional test cases rather than as a CI quality gate. A minimum coverage standard is useful, but developers shouldn’t blindly trust high code coverage — test quality matters.

TDD

Test-driven development

In Test-Driven Development (TDD), new functionality begins with a test. Writing tests first demands a clear understanding of specifications, functional requirements, and possible edge cases — a hallmark of TDD.

  1. Start by writing a test.
  2. Run all tests and check for failures.
  3. Write code to make the test pass. This code may be minimal and “just enough” to pass the test, without additional functionality.
  4. Run all tests again. If they pass, the new code meets requirements and doesn’t impact existing functionality.
  5. Refactor the code, improving its structure, algorithm, or cleanliness.

If necessary, we repeat this process starting with the new test and then the loop is repeated to extend the functionality.

TDD approach has some limitations. For example, it's hard to apply it to Big Data & Data Science projects, because of the data-driven part of those applications, it could also be hard to apply it to GUI or frontend development. Other then that Unit Tests are perfect for any software development life cycle.

Conclusion

Unit Testing is a powerful tool. It lets developers validate code early, speeds development in the long run, and keeps code reliable and maintainable. The benefits may not be immediately obvious, but in the long run, unit tests are your application’s safety net.

Additional materials



Buy me a coffee
Next post

More? Well, there you go:

Rumbling about Test Driven Development

Continuous Integration & Delivery main ideas

Data Science. Demystifying Hypothesis Testing