How to avoid common pitfalls that make testing difficult? Part one

Every developer has heard of unit testing. Almost everyone knows that they are useful. Many developers even write unit tests. But how many of them really know how to make code for which writing tests won’t be like a road to hell? How many developers treat writing tests with pleasure rather than like an unnecessary invention of the project manager? If you are familiar with unit testing but want to know how to write testable code and how to avoid common pitfalls that make testing difficult, then this series of posts is for you.

I assume that you know well what unit tests are and how the process of mocking dependencies between tested classes look like. As a brief reminder, the unit test can be defined as a short code that verifies the correctness of a given functionality of production code. The test should be short (for credibility and readability), have a clearly defined initial state, test only one functionality, be independent of the other modules, execute quickly, have clearly defined acceptance conditions, and, most importantly, the result of the test must be deterministic.

There are many strategies for creating unit tests. Among them, special attention should be given to TDD (Test Driven Development). In a nutshell, this approach assumes that code implementation starts with writing tests, and not a single line of production code can be written until the test that covers it is ready. This approach, although effective in the quality of the final code, clearly divides the programming community. On the one side there are arguments that in this approach the level of coverage of the code will be as large as possible. On the other hand, it effectively extends development time, especially when the project is at an early stage of development and its final vision has not yet clarified.

Among the strategies that can be encountered, writing tests can be done as a part of implementing of a given functionality, just after the production code is written. In other words, this strategy assumes (just like TDD) full coverage of the added functionalities with tests, however, allowing the developer to write the production code first.

You can also come across a strategy that says code now and tests later. An important disadvantage of this strategy is that the code written under this approach rarely meets the quality criteria enabling it to be tested without additional refactoring. It is worth noting that refactoring itself can generate errors in the production code. Finally, using this approach results not only in lower test code coverage (compared to previously discussed strategies), but also there is a risk that new errors in the production code can occur (for example during adapting it to the needs of unit testing). This, combined with writing tests by a person who is not the author of the production code, is a harbinger of an almost inevitable catastrophe.

I encourage you to write tests during the implementation of a given functionality – this is the most effective method of writing testable code. The resulting code not only can be characterized by high test coverage, but also a much higher quality, which obviously translates into work efficiency of developer. Opponents of this approach point out higher cost of code production. This cost, however, pays off at the first errors, modifications or problems with the resulting product, which are not only easier to detect, but also easier to repair thanks to the clearer and more testable implementation. Of course, high test coverage doesn’t have to mean that every single line has to be unit tested. It is a role of developer and project manager to specify this level – taking into account factors like budget, time to release, complexity of the code, necessary quality level, and so on.

Assessing whether to test all classes in the code, and whether to write tests first (as defined in TDD) or production code first, is not the subject of this article. The presented guidelines stay in isolation from the chosen code development strategy. The content of this series of posts is focused on how the production code should look like so it would be possible to create high-quality unit tests for it. The articles present several typical problems and potential pitfalls that can effectively slow down, or even prevent, testing of the selected functionalities. The described tips will be especially helpful for people who do not have much practical experience in writing unit tests in commercial solutions. However, the nature of the principles described here is so universal that everyone, regardless of their experience, should be able to draw useful conclusions from reading.

In the following posts we will explain  – with examples in C# – a number of rules that should be considered when writing easily testable code. Those rules are:

– dependencies to classes being tested should be passed (injected) in advance;
– class dependencies should be represented by interfaces or, at least, virtual methods should be used;
– calls to static functions should be wrapped;
– factories should be used when one need to create a helper class instance as part of the code being tested;
– the tested method should provide exactly one functionality.

Next part of the article in a week!
If you need help immediately or you want to grow with us, contact us:

    References to the entire series
    [1] Fowler M., Beck K., Brant J., Opdyke W., Roberts D., Gamma E., Refactoring: Improving the Design of Existing Code, 2011
    [2] Martin R. C., Clean Code: A Handbook of Agile Software Craftsmanship, 2015
    [3] Martin R.C., The Clean Coder: A Code of Conduct for Professional Programmers, 2013
    [4] Osherove R., The Art of Unit Testing: With Examples in .NET, 2014