Here is a diagram of the testing pyramid, courtesy of Ministry of Testing:

Testing Pyramid

On our base, we have unit tests. We’re basically just testing functionality of our code. We have integration testing in the middle, which is where we’re testing backend services and APIs. And then we have our end-to-end/UI testing up top, as well as optionally some manual tests. QA lives at the top, and usually it’s pretty expensive to run the tests that QA is involved in.

Coming from the bottom to the top, in time and expense, unit tests take milliseconds, integration tests usually just take seconds, and the UI tests take minutes. And of course, manual tests can take hours. As you go up the triangle your tests get more and more expensive. So we want to do most of our testing under the UI tests.

If we can push tests down, that’s the best practice to make sure that we’re not using too many resources or wasting our time with UI tests which can be flaky in some instances. Most of the time, developers are responsible for testing their code at the lower levels, because it’s much easier to write those tests for your own code. Someone else who hasn’t written your code might not have a full understanding of the expected result or behavior of your function or API.

E2E/UI testing has a bit deeper penetration, where we can hit everything that we need to, but we can’t necessarily root-cause where the issues are, so it’s less accurate as well. Whereas if you catch issues in your code ahead of time, you can save a lot of time. This also has a lot to do with the time it takes.

Testing is Like Car Racing

The way I like to compare them is that your unit tests are like drag races - they’re quick, and you can quickly diagnose issues. If you put your foot on the gas and your engine explodes, you can usually figure out what happened pretty easily.

Integration tests I like to compare to loop races like NASCAR. If you hit the gas hard enough there’s probably a few things that can happen, you might hit the wall, the engine might explode, or you might spin out. It’s a little harder to root cause where the issue started in this situation. And in terms of time, they’re a little longer than your drag races.

End-to-end/UI tests are more like rally races. It’s going to be kind of a crazy ride, there’s a lot that can go wrong and you can’t necessarily root-cause where the problem was. In the race, all you know is you popped the tire, not necessarily where you popped the tire. You probably have a good idea, but you’re not going to be able to guess as accurately as you would in a drag race or a regular loop race like NASCAR.

When the Pyramid Gets Inverted: The “Pyramid of Death”

Here’s a diagram of an inverted testing pyramid, also courtesy of Ministry of Testing:

Inverted Testing Pyramid

When the pyramid gets inverted, we like to call it the “pyramid of death.” That’s where your unit tests are taking up basically none of your total testing, integration tests about the same, and you’re relying on your UI and manual tests. You’re gonna spend more time trying to maintain your UI tests, and overall it’s going to be more expensive and difficult to maintain. Especially if your UI tests are flaky, you’re going to be spending a lot of time fixing flakiness rather than the correctness of the code.

When you have the testing pyramid right, it also helps you “shift left” your development. This means that along the development->testing environment->production pipeline, you want to catch all the bugs to the left-most as possible (i.e., as early in the pipeline as possible). This reduces downtime for your application, as well as developer churn.