Working on big projects with big code base composed mainly of “legacy code“, developer’s biggest fear is to introduce something new that might actually break something old. This fear freezes the project with some pieces of code that remain like engraved in stone because nobody wants to touch them.
The high level of risk in refactoring old code prevent developers from making brave decisions and rewrite pieces of their app that actually would really need to be modernised.
When I first heard about unit tests it was the very beginning of my career. At that stage, I did not understand the need of producing more code that of course will need maintenance and attention.
A program that tests another program?
Alan Turing would be delighted… Am I going to write unit tests for my unit tests to check if they are testing correctly what I am trying to test?
Of course, at that time I had the responsibility of very small projects, and my safety net was my boss that was taking the blows for me.
Then it breaks
At some point, suddenly it happened: I reopened one of those projects after years to fix a bug.
Opening that project my first thought was “Fire… I need fire… A big one!”.
It was just like trying to slay a hydra. Cutting one head was causing three to grow, and at the end, the only solution was to rewrite the module.
The level of risk was huge. There were many modules in the app that were heavily relying on the results coming from the one I was going to rewrite.
To solve this problem I needed some strategy. Isolate the module and try to produce the same expected results of the previous version, but this time with an eye for the unhandled edge cases.
Obviously, the project had just one test case with just one test in it: the one autogenerated by Xcode. Not very helpful.
It’s never too late to start writing unit tests
I found that the perfect strategy was to start writing unit tests for the code I was going to refactor.
- By testing the expected results I was able to establish a series of “must” that my new version had to conform in order to not break the rest of the project.
- If you must fail do it fast: Unit tests are a lot faster than a human being that manually reach the area to be tested.
- It was an investment in the future: Any developer stepping in my code will know from the expectations in my tests if they are breaking working behaviours and what is the result that the tested code should produce.
This is of course just an example where unit test made possible a smooth transaction between legacy code and modern code, but this is not the only reason why writing unit tests is a good idea.
Why should I write unit tests, then?
Some developer believes that if something is working today it will work forever. They ignore the law of entropy. The longer a piece of code will stay with poor maintenance the messier it will become, producing errors and unexpected behaviours.
With time new development techniques will come up, new APIs will be released and something you were doing in a thousands of lines of code might be simplified after one year to be done with better performances with just one hundred lines. It is always good to keep your code up to date and healthy refactoring it when needed. Unit tests will give you the power and the control over your project to help you to do that.
Save QA time
We don’t write unit tests just to not break stuff, but also to prevent edge cases and bugs that otherwise would touch the QA team slowing down the development process; some bugs that would have been caught by a test might even pass the QA process and go into production.
Better app design
To be testable your code must be executable in isolation, and it must be designed to do that. Designing your code in this way opens the doors to the kind of flexibility needed in a project that might need to scale up to implement new features in a continuous delivery environment. Writing tests taking advantage of dependency injection is absolutely a good example of producing scalable and healthy code.
Better code quality
Knowing that there is a test that you can trust and run when you want to check your code, will give you the level of confidence you need to experiment, take risks and write amazing code, finding the best solution to a problem, and not just a solution.
What should I test?
Any relevant piece of logic.
I saw so many times tests for getters and setters just for the sake of having a high score of code coverage. Code coverage is just a stupid number that does not represent the quality of your tests, but just the amount of code tested.
We should trust Apple enough to believe that a getter or a setter will always work unless we are talking about a computed property or if setting a value produces side effects that we might find useful to check (in the property observers for example).
Possessed by the fun of testing their code someone starts testing the integration with well-known frameworks and libraries like Alamofire and CoreData. To do that might be useful for a young team to be sure that best practices are adopted as well as the correct use of the library is in place, but in a team of veterans, this might be a useless waste of time.
Some team decide to write integration tests with rest APIs to make sure that their implementation is still valid in the future, but in some other team any change to the backend might always be part of a transition process where the mobile apps are involved, therefore everyone is acknowledged of changes and can update their code accordingly.
The best way to decide what to test is always to sit down with your team and define together which are the best practices to follow so that everyone can have the best experience in the development process.
It will not be easy
Yes, the hardest part will probably be to convince your team that unit tests will actually help to speed up the development, instead of slowing it down. You will actually have to find good points to prove that unit tests are needed for your kind of project.
If you manage to convince your team, you will probably find hard to put in place tests for a code base that was not designed to be testable, without dependency injection to help you out.
You will always have someone in your team that will tell you “I told you” when you will have to refactor code just to make it easy to be tested properly.
Be patient, do your best, adopt best practices and make it work. Understanding the importance of unit tests is the first step through the door of the wonderland of clean code world.
If you are starting a new project it’s a good idea to build it testable. If the project is not a green field, it is a good investment to write unit tests for it and refactor your code to make it more modern and open to future opportunity and changes. You’ll find out that all your pain will be rewarded with fast QA process, and fast bug fixing.
That said, remember: never impose your point of view on the team. Just like any decision, this one has to be made as a team. Sit down and decide with them which are the best practice to follow, what to write unit tests for and develop with them a process and the team best practice to reach the best code quality that you can deliver.