How it started?
My current project is a big enterprise application from the insurance domain. Frankly speaking, we didn’t do almost any unit testing for a long time. One day I listened to a podcast about unit testing and there I heard a sentence: “If I would work in a project where developers don’t use unit tests, I would still write them locally without pushing them to the source control. And I’d leave the company as soon as possible.” This inspired me and I thought it would be useful to have unit tests in our project as well. I will describe the process of how we introduced it and what benefits it gave us.
Why unit tests were not used?
One of the main reasons for not using unit tests is the wide usage of layered architecture. There are a lot of external dependencies in domain logic which means that writing unit tests is hard. Such tests need to contain a lot of ‘mocks’. As a result, test methods get long and are hard to read. Moreover, it often happens that when code changes or is refactored, you need to change your test methods as well which is discouraging for developers. Maintaining such a test suite becomes a pain and sooner or later developers scrap unit testing projects. The other reason is the lack of knowledge of what benefits a unit test provide. Surprisingly, the ability to catch regression errors is not necessarily the main advantage of using them. The final factor was the approach towards it. Everyone thought that we can’t use unit testing at all but this appeared not to be true.
Benefits of using unit tests
I decided to start by gaining some knowledge as it was very poor at that moment. I went through an excellent Pluralsight course Building a Pragmatic Unit Test Suite by Vladimir Khorikov. The author explained how to build valuable unit tests and described obstacles in using them. That was exactly what I needed. Here are the benefits of unit test that were identified to be most useful for our project:
- The faster pace at introducing changes to existing code – no need to have a running local environment for testing code changes
- Better code quality – code with unit tests is written with more thought, code is better structured and divided into components
- More scenarios covered by the developer – testing different scenarios is simple and fast therefore developer can spend more time on testing
- Developer confidence during refactors – developers are more eager to refactor the code when the code is covered with unit tests
- Unit test method names give more information to developers about code behavior
- Ability to execute frequent actions used for diagnostics (payload deserialization, calling a service) – no need to store such code in console apps outside of source control
The list is quite impressive and surely it looks like the project can gain a lot by using unit tests!
Choosing testing framework & adding first unit tests methods
The next step was to add the first unit test methods to the project. To do this we had to choose some functionality for which we wanted to add unit tests. Ideally, it should have some business logic inside and a few external dependencies. We chose the existing code used for calculation and different financial amounts in one of the subsystems.
We also had to choose a testing framework and for this, we decided to use NUnit and Moq since some of the team members had experience in working with them. Finally, we managed to add first unit tests methods.
Training session with the team
It’s crucial to share knowledge about new technologies/processes with a team. Hence, we organized a session. The major goals of the session were:
- Explain the benefits of using unit tests
- Demonstrate unit tests created in a project
- Inform about chosen testing frameworks
- Explain what code areas are suitable for adding unit tests
- Explain how to build a valuable unit test
- Encourage to start adding unit tests on their own
Additionally, a summary was added on the project documentation webpage with the crucial information so that it is easily accessible. It was advised that during the planning sessions, team leaders will help other team members to identify if unit tests should be added for new functionality.
How to write a valuable unit test?
Many fail in writing and maintaining unit tests because it is not a piece of cake. It is necessary to have knowledge of how to do it properly. One of the key concepts is to design your code so it looks like a module that has a clearly defined entry point (ideally one) and output. If that’s the case it looks like this:
public decimal CalculateCost (List<ProductItem> productItems, ShippingInfo shippingInfo)
Unit tests covering such components would be easy to write and understand. Also, when implementation detail would be changed, the test will verify if the output is still the same.
Here are some other tips for beginners:
- Unit tests are ideal for behaviors handling simple types (like calculations, operations on dates, texts, etc.)
- You don’t need to add unit test for every piece of code you write, start with areas that contain a lot of business logic
- Try to separate your business logic code from external dependencies (like a database) so it’s easier to test
- If the external dependency is necessary for your business logic code, use an interface so that it can be easily replaced in unit test by ‘mock’ in your business logic code, use an interface so it can be easily replaced in unit test by ‘mock’
So, people began to add some unit tests to the project. As a next step, we turned on unit test execution as a step in our CI pipeline. If a unit test fails after a code change, the developer gets notified about it and the change must be adjusted. Sometime later we also organized another training session about using mocks and stubs in test methods. We also showed where unit tests were added to a project and why.
It turned out that, unit tests are very useful in our project, especially when a developer needs to do a lot of steps in the application to reach the place when the code needs to be changed. With unit tests, it’s much faster as you can start debugging immediately. Also, we had a lot of code outside of the main source control that was used for diagnostics, etc. We created unit test methods for it so they are in a known place and are easily available to everyone. The process of introducing unit tests turned out to be successful and I encourage everyone in a similar situation to try it as well.