One way to spot a lack of professionalism is missing unit tests in a project. It is frightening how many dev teams do not write any tests and rely only on manual testing.
In the following paragraphs, I will try to convince you about the importance of writing unit tests.
What are unit tests?
Unit tests are the smallest tests you can write. They tend to test different parts of your code (mainly single methods). Unit tests should be very fast so that you can run them often.
It is important to stress that they do not test how different modules interact or external dependencies. That is the job of the integration tests.
Your tests should focus only on your code and its logic.
They should not depend on and do not work over real data from an external source. To be fast, they rely on fake data loaded into memory.
Unit tests have a simple lifecycle – initialization of needed resources (loading fake data, creating fake services), executing, and freeing resources.
Why should you write unit tests?
Writing unit tests does not only help you not to introduce bugs in the future. Often writing tests, you can spot one more case missed. Also, boundary cases are easily spotted. Even if you do not practice TDD, they will help you better understand the code logic.
Unit tests are like insurance for a future disaster. No matter if you want to extend functionality or refactor it, it is a risky business doing it without a nice suit of them.
Imagine you have inherited a legacy system. Your task is to extend the authentication module, so a new way of authentication method is possible. In one case, you have a suit of several dozens of unit tests, and in the other, you have nothing. In the first case, when you broke something (believe me, you will), you will receive feedback in a matter of minutes. In the second case, the best hope is QAs catch it manually before it goes to production.
How to start?
The most crucial factor is your code. To be able to write useful tests, your code should be clean. You should be able to test your classes and methods in isolation. If this is not possible, your first task is to start breaking dependencies and refactor. Such a refactor is not an easy task. Changes should be small and incremental. After each change, you should introduce new unit tests.
Apply already established good practices and principles, if and where possible.
What to test?
As I said above, unit tests should test your code. Test these classes where the core logic resides.
I have seen projects with good code coverage, but at the same time, most of the tests tested unnecessary things.
How to structure unit tests?
To some extent, that depends on your current solution structure. My preferred way is to have a project with tests for each project with a deployable code. So if I have a project like NotificationServices, my unit test project will be NotificationServices.Test. For each service class in the NotificationServices project, I will have one or more test classes in the NotificationServices.Test (you can group them in folders if more than one).
In short, the test project mirrors the code project.
Resources (for C#)
Unit Testing Frameworks:
- XUnit (a group of frameworks, not only for C#)
- Visual Studio Unit Testing Framework
Frameworks for mocks:
- JustMock Lite
- Autofac.Extras.FakeItEasy (Autofac extention)
Best practices as a summary
- Tests should be fast;
- You should test your code, not external dependencies;
- Your code should be tested in isolation;
- Test results should not depend on random data/factors;
- Don’t write unit tests just for higher code coverage. Add them where needed.
- Test with a variety of test cases;
- Test border cases;
- Test for failing cases;
- Structure them in a way you can work with them quickly, without cluttering your deployable code.
- Name them with meaningful names.