This post is a part of How To Ship Maintainable Product series
At first glance, unit testing seems to not provide business value. From the product perspective unit tests can be considered as a waste of time, resources and money. Especially when a company barely validates business idea with an MVP prototype. Having such context in mind we need to have really good arguments for implementing unit tests.
Purpose of unit tests
Unit tests have several reasons to exists:
- Guide developers to implement components in a loosely coupled way - developers need to write code in a testable manner.
- Provides an automated way to do regression tests - test can be executed automatically on Continous Integration server.
- Gives the developer living documentation on how to use units in application - test provides a sample of component usage.
Some development teams may not see the benefits listed above. Others will rely on integration tests only. Unit tests are not required in software development, but I strongly recommend to introduce them.
Test single unit
The sole purpose of unit tests is to
test a single unit. Nothing more. Very often I see code where tests, that suppose to check only one element, check much more (sic!). It is a fragile practice because it requires refactoring in many unnecessary places when changes occur in one code class.
One logical test
It is very important to test one logical behaviour in the examined unit. It does not mean that technically we should have one assert per test. We should ensure that all interesting fields have expected values in the examined test scenario.
I know it is very tempting to add
just another check to already existing test. Unfortunately, that way the test purpose is becoming unclear. Other developers will feel confused about the test original idea and entitled to introduce more different logical checks.
We should check only one logical behaviour per unit test.
A hard thing about testable components
The hard thing is to design a testable component. Mocking frameworks were introduced to simulate the interaction with external components. This way we can isolate the component we want to test from other parts of the system. A common example here is mocking the database when testing services or controller when testing views in MVC design pattern.
Tests should be small. I would say that 8 lines is a reasonable test size in C#.
Large tests are hard to grasp, hard to understand and hard to maintain. Write small unit tests, please.
AAA - Arrange, Act, Assert
Triple-A is a known way of structuring tests code. It is considered a best-practice. Such a test has 3 sections: 1) Arrange - here all mocks and other support code is stored. 2) Act - preferably one line of code where the tested method is triggered. 3) Assert - here the actual output is compared with the expected output.
By introducing AAA test style we make the maintainability simpler and cheaper because the developers know what to expect in tests. Each test is expected to have the same structure.
The naming convention is one of two biggest challenges in software development (the other one is caching). Test name should reflect the tested scenario. It should be aligned with
one logical test. Names should not be extremally long, but usually, it is ok to have them little longer than business method names.
Naming tests like Test1, Test2… TestN is unacceptable. Such tests have no value. Even they check something it is hard to say what is the expected behaviour. Additionally, they introduce confusions and questions.
Test-driven development is a software development practice. The developer writes test cases based on the business requirements. In the beginning, all tests fail, but as he progresses with business code, the tests start to pass.
From my experience, TDD is excellent in certain cases. I would recommend it for business logic validation, but not for MVC tests.
What unit tests are suited for
As it was discussed above, unit tests ensure proper behaviour of
single component/element etc. in the product. On higher, business level, the components interact and share data with others. Unit tests will not protect us from sharing the data between wrong components. Components may be perfectly valid individually, but combined together they may work faulty way.
When unit tests are not needed
There are cases when unit tests can be omitted.
Usually, this can happen when we do one-time work, that will be thrown away soon after. For instance, we need to import sophisticated data and we write a script for that.
Another possibility is when we write a super-simple prototype to validate a business idea. I would agree to skip any tests until the prototype is proved useful. After a successful evaluation, when proper and structured development starts, unit tests have to be added to the already existing codebase.
To ship a maintainable product we need to have unit tests in place. They will ensure us that new changes will not break old features. Unit tests have a number of benefits as listed in
Purpose of unit tests.
Unit tests are a great selling point for vendors, but they are rarely done right.
There is only one thing to remember:
well designed and structured test will save us extra costs. A bad unit test will cost us more.