Unit Testing Pitfalls

Image credits: Photo by Lukas from Pexels

Unit tests are one of the most useful tools in software engineering. They provide us with fast feedback after code changes. A good set of automated unit tests gives us confidence in our changes. It reduces the risk of breaking things by accident.

On the flip side, writing effective unit tests can be quite challenging. Most seasoned developers have seen test suites that were painful to maintain. Hence, let’s take a look at common mistakes when writing unit tests.

Overspecification and too many abstractions

Sometimes, you will encounter tests that verify way too many meaningless things. Usually, those tests are quite brittle and break easily when a small implementation detail is changed in the code under test. This could happen in an attempt to reach an artificial code coverage goal. Other times, there will be so many abstractions in the test that you can barely understand what is happening. Excessive use of inheritance and polymorphism is one of those examples. When you look at a unit test and the logical flow is unclear, chances are decent you have run into this problem.

The solution to this is to keep in mind that tests should be structured very simply and clearly. Avoid complex class hierarchies and artificial abstractions if you can. Don’t take the DRY principle too far. Some degree of repetition is okay if it makes the tests clear and readable. Usually, tests should consist of a setup part, a test call, and a verification part. Those sections should be clearly visible and not hidden in some abstraction layer just to avoid repeating a few lines. Of course, feel free to extract helper methods or classes (preferably as delegates) if they form a logical unit and help in reusing large portions of code.

Finally, your tests should verify behavior that is visible from the outside, even if it’s just an internal API call. Avoid verifying implementation details. This leads to maintenance issues down the road.

Overdoing mocking and stubbing

Code usually depends on other code. This can be a challenge since unit tests should focus on small, isolated parts of our program. We don’t want to test the whole application or even 10 % with a unit test, but rather a specific function, method, class or similar. Therefore, mocking and stubbing are frequently used to replace those dependencies. It isolates our code under test from other parts and can be very helpful for this reason.

Sometimes, this mocking and stubbing goes overboard, though. Typical signs are lots of long and complicated setup sections that specify endless chains of stubbing and mocking. One can quickly wonder if we are actually testing something real here or testing the mocks and stubs.

Try to avoid this issue if you can. Ask yourself if you can restructure the code under test to have fewer dependencies or better encapsulation. As an example, you might decide to separate a single huge class with many fields into multiple smaller classes, each having less dependencies. Then every class can get a set of simpler unit tests that are easier to maintain.

Unsuitable use of unit tests

Not every part of a program is well-suited for unit testing. Sometimes, it’s not the right tool for the job. Examples could be very rigid code sections like dependencies on static methods or final or const structures where you cannot reasonably isolate the code under test or introduce mocks. Other times, you might have very trivial or generated code parts like getters and setters. Moreover, there are other parts that are naturally less suitable for unit testing, like graphical layouts or code with external dependencies like database access layers.

The solution to this is to consider other testing types. Integration layers involving databases or similar are usually better tested with integration or end-to-end tests. This is much more valuable than mocking DB queries, as you cannot verify the actual queries in that way.

Summary

Writing unit tests is good, but we need to ensure they are meaningful and maintainable. This is easier said than done. It helps to know some common pitfalls in order to avoid them.

What’s your experience with unit testing? Have you seen some problematic tests that were hard to maintain? How did you handle those? Feel free to tell me in the comments.

#SoftwareEngineering #UnitTests #SoftwareQuality #TestAutomation #TestMocks

Bastian Isensee
Bastian Isensee
Software Engineer (Freelancer)

Quality-driven Software Engineer focused on business needs, knowledge sharing, FinTechs, Golang and Java