It's 2009.
You only know a few people who own an iPhone.
You're itching to finally use jQuery on one of your next projects.
When Mike Cohn publishes a new book, and shares his idea for a "testing pyramid":
"Write more unit tests!"
"Avoid end-to-end tests as much as possible!"
This pyramid helped devs figure out what tests to write, and we've been following this advice religiously ever since.
But now it's 2024 — and the world has changed a lot since then.
Let me show you why the testing pyramid has become less useful over time, and why we should all just draw our own shapes anyway.
It's time to bury the pyramid and draw something better.
A quick refresher on what the Testing Pyramid says:
The original idea was written about by Mike Cohn in his book Succeeding with Agile, which was published in 2009. In the timeline of software development, that's ancient history. The way we write software has changed a lot since then.
He also comes from the C++ and Java world, which is heavily object-oriented, and faces different testing problems than we face in web development with Javascript (and now Typescript).
So here's the problem.
This might have worked well in the past, and for a particular tech stack, but we need to re-examine the pyramid to see if it still holds up today for web development.
The standard tooling that we get to use as web devs is really good, and this causes the pyramid to become more of a square.
First, let's look at end-to-end tests.
The WebDriver Protocol and Chrome DevTools Protocol have completely changed how we write and run end-to-end tests. Tools like Playwright and Cypress use these protocols to provide a much better testing experience compared to 10 years ago.
These modern tools have built-in waiting and retry mechanisms. When a test fails because an element isn't visible yet, they'll automatically wait and retry before failing. This makes tests much more reliable and less flaky.
Compare this to older tools like Selenium which still don't have these features built-in.
With Selenium, you have to manually implement waiting and retry logic, which is error-prone and time-consuming.
The result is that writing and maintaining end-to-end tests is much easier than it used to be. Tests are more reliable, debugging is simpler, and the development experience is significantly improved.
Now, writing e2e tests is easier, and running them can be quite fast.
So the narrow, pointy, top of the pyramid has gotten wider and less pointy because of this.
And those unit tests?
Well, Kent C. Dodds has done a great job of pointing out that adopting Typescript changes the shape of your testing strategy as well.
Before, we needed to write lots of defensive code to check if we passed weird things into methods. Now, we can use Typescript to make sure that never happens:
// Beforefunction add(a, b) {// Check if a and b are numbersif (typeof a !== 'number' || typeof b !== 'number') {throw new Error('Both arguments must be numbers');}return a + b;}// Now with Typescriptfunction add(a: number, b: number) {// No need to check if a and b are numbersreturn a + b;}
We still need to test our business logic, but by using Typescript, we can write fewer unit tests.
So the wide, flat, base of the pyramid has gotten narrower. Our testing pyramid is becoming more of a square.
But all of this is rather beside the point.
The thing that you should be doing is drawing the testing shape that's right for your project, not borrowing an idea from someone else.
Let's think about version control for a moment.
While there are many options available (Git, SVN, Mercurial), there's a clear winner. Git has become the de facto standard, and for good reason. If you're starting a new project today, there's really no debate about what to use.
(Unless you're just FTPing files to a server, but then I can't really help you.)
Some technical decisions are like this. While multiple options exist, we've collectively figured out the best approach.
This can only happen when there is a best approach.
However, testing strategy isn't one of those things. It's more like city planning — there's no single "correct" way to design a city. It depends on geography, population, climate, culture, and more.
What works perfectly in one city might completely fail in another. This is why we have so many different kinds of cities in the world.
The same is true for streaming services. You can't say that Netflix is the "best" choice, because it entirely depends on what you want to watch, whether you're already paying for Prime, and a bunch of other factors.
And so we have dozens of them, instead of a single streaming service that rules them all.
We can see this happening with testing strategies, too. There is a proliferation of testing shapes to choose from:
Yes, these are all real testing shapes that people have written about, which means that there are probably more being used that aren't written down.
The reason why we can't agree on what a good testing strategy looks like is because there isn't a single correct answer.
We each need to figure out and decide what the best approach is for our project.
This is where things get challenging.
It's the part where I say, "it depends", and fail to give you what you're looking for — a single, correct answer you can use in your project right now.
What I can give you is a framework to help you figure out which shape is right for your project.
There are several dimensions to consider. Each one of these can affect your testing strategy. And because these things can change over time, your testing strategy should change over time, too.
Here are some of them:
I'm sure you can think of many more that affect your own testing strategy.
This is as far as I can help you (at least in an article format).
You need to do the work to think about your own project, and figure out what the right shape is for you.
Maybe it's a circle, or maybe it's a sphere? Maybe what you need is a testing Klein bottle.