In my previous post, I explained why you need automated tests. It’s cheaper, and more fail-safe. But not all tests are equal. And there is a place for each type of test. In this post, I will try to explain the basic types of automated tests in software development and lay out their use cases with advantages and disadvantages.
Most developers are introduced to automated tests through unit tests. The role of unit tests is to test a single unit of code.
What exactly is understood by a unit of code isn’t clear. But a unit test usually tries to use as little extra components as necessary. If the piece of code under test uses other components, they are often “mocked out” (also called stubbing or faking): replaced by a fake component whose behavior is controlled by the test.
Unit test should run very fast in order to provide quick feedback to the developer. They should also be small and readable so that other developers can quickly understand what’s going on. Finally, they should be deterministic: every run should produce the same output.
When developers write unit tests, they are often forced to use a good design and architecture of the code. Badly designed code is hard to test. Which is why automated tests should be written in conjunction with the actual code. It leads to better code and holds back technical debt.
In addition, it provides a way of being sure that no new bugs are introduced when someone changes the code. Just run the tests and you can be fairly sure that you didn’t break anything that worked previously.
A possible disadvantage of unit tests is that it requires some experience. Lesser experienced developers will work slower for a while and often find the process frustrating. Which is why they should be coached well by a more experienced developer.
Remember how I mentioned that unit tests try to avoid using other components? With integration tests, this isn’t necessary. Integration tests are all about testing the integration between different components.
In practice, developers can get into lengthy discussions about whether or not a test is a unit test or an integration tests. I don’t believe it actually matters, as long as a team can agree on the difference for that specific project.
End-to-end Tests or UI Tests
UI tests are a kind of end-to-end test. The idea is to test as many moving parts of the application. In case of an application with a user interface, we call them UI tests. But not all applications have a UI. In that case, they’re just called end-to-end tests.
It may seem attractive to ditch all tests in favor of these. After all, we want to test the entire application don’t we? Unfortunately, it’s not that simple. End-to-end tests tend to be slow and easy to break. Developers call them “brittle tests.” This means that the test can easily break, even though there isn’t actually an issue with the application.
The Test Pyramid
The test pyramid is a guideline for how many tests of these types you need. I’ve written about it extensively for NCrunch, a company that creates a testing product for .NET applications. The central idea can be shown in this graphic:
You want more of the “smaller” tests that don’t run many parts of your application. And less of the tests that require a lot of parts of your application.
This ensures that they can run fast and don’t break as easily.
Where To Start?
I outlined three types of tests here, but there are even more types of tests: property-based tests, contract-tests, regression tests,… Some of these definitions often overlap.
My advice to you is to get started with unit tests. They are the easiest and provide the highest value.
Developers will have the least amount of trouble getting started with integration tests. Even though legacy projects might seem a good fit for end-to-end tests because they are hard to test, it’s still better to start with the smaller unit tests.
Unit tests will give developers faster feedback and will start improving the “legacy code situation” you’re in.