Do you write automated code tests? If you do, why do you test the things you do? What techniques do you use?
In this post I'll cover why we should test code and the different ways you can write valuable tests.
Why do we test?
First, let's think about what automated test provide. What's the point?
Tests give us confidence. When we write code we want to be confident that it's working as intended. You might be sure your code is working at the time of writing it and not see the value in spending further time writing tests, the benefits really increase over time. As our application and code changes, tests give us confidence that the changes made do not unintentionally break existing functionality. This is valuable because it means we can move more quickly without worrying whether there have been unintended consequences and enables us to ship a high quality product more regularly.
Automated tests are inexpensive compared to manual testing. You can run thousands of automated tests in seconds. It would be extremely difficult to manually test your entire application every time a change was made. Also as your application grows you would have to hire more manual testers who's salary you have to pay. On the other hand, automated tests can test your entire application on every small change and run cheaply or even for free. For example, it's simple and free to set up automated tests for a Node project using Github Actions!
Of course there is still a human element to automated tests because they must be written by someone in the first place. However, since the tests are run by a computer they can be repeated over and over again reliably and produce the same results. If you test manually there is much more room for human error.
Types of automated testing
There are three main types of testing we can utilise.
- Unit tests
- Test individual parts of your application in isolation. They often involve mocking dependencies so you can truly test the specific implementation of the code in question. These are most useful for testing complex logic.
- e.g. Test a service that depends on a database repository performs the correct logic, but mock the repository so we can test the service in isolation.
- Integration tests
- Test that several units work together and use their actual dependencies rather than mocks. Generally integration tests will test input and output of a function.
- e.g. Test a service that depends on a database repository without mocking. This way we can test the service performs the expected actions on the repository and returns the correct result from the database.
- E2E tests (end-to-end tests)
- Test the full stack of your application by imitating how an external person or program would interact with it.
- e.g. Spin up an instance of the real application, click a button on the screen and test that the user is presented with the expected result, like it has displayed an alert or navigated to a new screen with the expected content.
So how do we make tests valuable?
Over the last year I've been thinking more and more about what makes tests actually valuable. It's a valid concern that tests can add a slight overhead at the time of writing them, so we want to make sure our tests are going to provide value in the long run.
For a while I thought it was vital to test every line of code and try to achieve 100% unit test coverage. I've recently changed my opinion on this though, and here's why.
It's important to consider time vs value. As we discussed earlier, we write tests to give us confidence. With that in mind, do we really need to take the time to write a test for a very simple function we have when we're already confident it works fine just by looking at it?
I believe, rather than aiming for a blanket 100% test coverage, you should aim to test areas which are particularly critical or areas that have complexity and you need extra assurance that things are working properly. I also believe integration and E2E tests provide the more value than unit tests. Firstly, your users are the most important factor of your business. This means it's important to test your application from the view point of the end user to ensure it's working as they would expect.
Last year I had to perform a large .NET solution refactor which involved moving almost all files in the solution. Unit tests mostly still passed, even though the entire application was broken, because they were only testing the individual units in isolation. The real value was having integration tests. Once these had passed we had the confidence that the application was actually working and could deploy it.
Tests are super important and can definitely be valuable in the long run. Here are some key points to take away and consider in your existing and future projects.
- Write unit tests for complex logic but don't worry about testing every single line of code in other places.
- Write integration and E2E tests for the majority of your application to give you the greatest amount of confident your application is working as expected.
- When writing tests, consider how much value and confidence you are getting for the amount of time it takes to write. It might seem a pain to spend a few extra hours writing tests, but it could save you 10x that in manual testing or bug fixing in the future!