Refactoring

Refactoring

Share this post

Refactoring
Refactoring
How to Test Software πŸ”
Copy link
Facebook
Email
Notes
More

How to Test Software πŸ”

A practical and opinionated take on one of engineering's most divisive topics.

Luca Rossi's avatar
Luca Rossi
Mar 23, 2023
βˆ™ Paid
28

Share this post

Refactoring
Refactoring
How to Test Software πŸ”
Copy link
Facebook
Email
Notes
More
Share
Upgrade to paid to play voiceover

Testing is an ever-controversial topic in software.

You would be hard-pressed to find any engineer who doesn't believe that testing is crucial to their workflow. Despite this, many teams struggle and are not happy with it.

Additionally, the general consensus around what good testing looks like seems to shift all the time. Testing is so ingrained into all parts of the dev workflow that any change to such a workflow leads to changes to testing.

For example, introducing feature flags, or switching to trunk-based dev, or adding or removing a Staging environment, or using AI to write code may all bring changes to your testing strategy.

In this article, I will write my take on what good testing looks like, and suggest a practical workflow that works today, in 2023.

Here is our agenda for today:

  • βš–οΈ Contracts vs Implementations β€” an important mental model to reflect on testing.

  • πŸ”¨ Regressions β€” figuring out the true ROI of tests.

  • πŸ—‚οΈ Types of tests β€” a basic classification with the main types you should know.

  • ⛰️ Testing Pyramid β€” a classic testing model made popular by Martin Fowler.

  • πŸ† Testing Trophy β€” a modern testing model which I personally prefer.

  • πŸ”„ My Testing Process β€” we put all of this together to design a practical testing workflow.

  • ✨ Other types of tests β€” we go beyond the basics with chaos engineering, load, security, and data testing.

  • πŸ’¬ Community examples β€” how Product Hunt and Swarmia do testing.

  • πŸ“š Resources β€” as always, further articles and resources to learn more

Let’s dive in!

βš–οΈ Contracts vs Implementations

Our relationship with tests is similar to the one with docs, security, and all those investments where we sacrifice something today for some benefit in the future.

Therefore, we should write tests only when their future value is higher than their writing effort. However, figuring this out is easier said than done.

To help with this, it is useful to think that any piece of software is the combination of a contract and its implementation. This stands true at any level of granularity, be it a small function or a large component.

  • πŸ“ƒ Contract β€” specifies the behaviour in terms of what outputs are produced from what inputs.

  • πŸ”¨ Implementation β€” the internals of how such transformations are made.

All types of tests verify that some contract is respected, while treating the implementation as a black box.

The various types of tests, such as unit, integration, or end-to-end, differ mostly in the scope and size of such contracts and black boxes.

For example, the way a UI works when you click things around is a contract between the software and the user. Similarly, the signature and semantics of a function represent a contract between the function and the other code that invokes it.

πŸ”¨ Regressions

Tests avoid regressions by enforcing these contracts whenever you make changes to their underlying implementations.

Tests are also useful for other things, too, such as documenting what these contracts are about or helping create better designs (e.g. with TDD). These benefits are sometimes controversial, but even if we agree to all, we are still talking of secondary stuff.

Writing and maintaining tests is expensive. If they didn’t catch regressions, we wouldn’t write them, period.

So, based on how and when the contract or the implementation changes, we have three scenarios:

  1. Contract doesn’t change + implementation doesn’t change β†’ Test is not useful

  2. Contract doesn’t change + implementation changes β†’ Test can catch a regression

  3. Contract changes + implementation changes β†’ Need to change the test

The only scenario in which a test repays itself is the second one. In the first one, the test is irrelevant, while in the last one, it is even a liability because you have to update it to reflect the new contract.

So, you want to invest in tests whenever there is:

  1. High chance of implementation change, and

  2. Low chance of contract change

Based on that, just how effective are the various types of tests? Let’s list them first.

This post is for paid subscribers

Already a paid subscriber? Sign in
Β© 2025 Refactoring ETS
Privacy βˆ™ Terms βˆ™ Collection notice
Start writingGet the app
Substack is the home for great culture

Share

Copy link
Facebook
Email
Notes
More