Unit testing philosophy

Background

Unit testing has been marketed as a silver bullet, but that obviously is not true. This article is to discuss what benefits one should expect and at which cost. I have written down some statements that have been said about unit testing in order to see to what extent they are true. The aim of unit testing is to lower the total cost of writing and maintaining code.

Expected benefits ??

  • Code is always tested. Both within modules (lets say service-classes) and between them. Opens up for a broad discussion on what “tested” means.
  • Safer to refactor code. This applies to smaller refactorings within a module, but also bigger ones where functionality moves from one module to another.
  • Faster feedback on what and where the error is. Unit tests are small and point out the exact problem.
  • We gain better understanding by working with the problem from two different angles – code and how it is being used. Therefore we get higher quality of code.

Overhead

The expected overhead in coding is about 30%. The cost is initial and is earned back in a later phase of testing and maintaining the code. True?

Assumptions

  • Tests are less likely to change than the code. This would support the case that the overhead cost is initial only.
  • Test are based on the client requirements. Therefore they are less likely to change. Refactoring the code does not affect tests.

Layers

The big question

  • A – Should a unit test only focus aspects that the specific service handles?
  • B – Or should it even include aspects of other services or persistence?

The answer to this question has whole bunch of consequences. If it is a yes/no questions. Unfortunately I have found no one actually talking about these aspects.

(A) If we are using repositories and unit-testing a service. Then we should not mock data-objects, but instead mock calls to the repositories.
(B) We use real repository classes but mock the underlying data objects.

Let’s say our service that we test has to block for non-authorized attempts.
(A) Mocks the underlying IAuthorizationService and fake that it return “no access”. We can verify that we called a method on IAuthorizationService. We would have to refactor the test if we switched to overload method in IAuthorizationService for some reason (eg refactoring IAuthorizationService).
(B) Mock the data-objects and call the real AuthorizationService. Requires knowledge of how permissions are stored. Should we mock IAuthorizationRepository and so on?

Problems

There is a high number of tests failing because of invalid mock data.
Fixing bad code – eg moving logic from a controller to a service – would require re-writing tests. Risk for “just mock that call”.
Attitude “just get the tests working” produces bad tests and false security. Belief that high code coverage means tested code.