Design Smells: Fragile Unit Tests

Introduction

In Uncle Bob’s book on Agile Development Principles, he mentions 7 design smells. These are all very good articulations of what it means to have clean, maintainable and extensible code.

What he does not mention is that there is another smell: Fragile Unit Tests (FUTS). Perhaps the reason no one thinks about this, is because not many people actually do unit tests at all. If you code has fragile unit tests, you’ve surely got even more fragile code. Having a solid foundation of tests acts as a safety net, but when your net has holes, it will do no more good than just adding to the maintenance of your program.

After having read Kent Beck’s article on Just Ship, Baby, I have become more wary of unit testing for the sake of unit testing. I have since then come up with my own rule of thumb for TDD. If it hurts to do something, then you’re probably doing it wrong (or don’t do it at all).

Here are 3 other testing smells that I think are good indications of your code having FUTS and in turn, lead you to take notice of more subtle design smells lurking in your code.

Zombie Unit Tests

With unit testing, it can quickly become a giant blob of ugly code. You’re constantly maneuvering an obstacle course of external dependencies and as you do, you’re not really paying attention to the mess of prior tests you’ve written. You’ve started in earnest with very solid TDD principles, and promised yourself to go back and refactor those duplicate lines of code. As you’re getting further, you fall behind in your refactoring, but hey, they’re just unit tests, not production code!

You’ve finished enough to get 80% code coverage, and breath a huge sigh of relief, never to embark back on that class again. Like Attack of the Killer Tomatoes, you worry that one day, your giant class of four or five unit tests will come storming down 5th Ave heading directly for your head! So our rule is, If you are dreading your unit tests coming back to haunt you, then your tests are already unmaintainable.

Making a Mockery of Your Code

Legacy systems are hard to test. You’ve joined a company and congratulations, you’ve just been awarded the worst inheritance ever: 600K of legacy code. You try your best to add unit tests, slowly unweaving the external dependencies, and realize, there are still more external dependencies. This is something you should recognize as bad design according to the Rigidity design smell. If you can’t inject dependencies then you can’t test without the dependencies. So what is your only recourse? Mock objects. There are several drawbacks to mocks. I consider mocking to be the last resort or last line of defense against legacy code.

The danger with mocking is if you end up mocking out too much at the beginning. You know its going to be hard when your code is considered “legacy software” (probably in 3 months), so why put yourself through it now? Heavily relying on mock objects is a good indication that your design has some serious flaws. Even with JMock 2.x, you’ve got several sequences or orders that need to be mocked out exactly in order for the test to fail. So our rule is, If your test relies on a lot of mocking, you are likely violating OCP.

Impersonating is Fragile

Along with heavily using mock objects, the other key insight is that mocking objects and not roles creates fragile unit tests. Roles are defined by interfaces, which can be implemented in a variety of ways. If you mock out concrete classes, you are constrained to the internals of that particular implementation, and now you’ve violating the The Interface Segregation Principle. Thus forcing a tighter coupling between your code, and your tests are not proving anything except for this design flaw. The only time you should mock out a concrete class is if you have no way of changing that class (e.g. EJB2.x). This was also mentioned by Steve Freeman, co-author of jMock library. jMock provides a class to do so, called Imposteriser but it has been moved into the legacy module. So our rule is, Prefer mocking roles and interfaces, than objects and concrete classes.

Conclusion

These a few that I have taken note of and have made an effort to be wary of when I’m practicing TDD. Recently, I’ve been more inclined to define contracts first, rather than tests, in order to maintain a more aligned adherence to the domain problems. Setting up your contracts as though you were the client has helped a great deal in deciding what to test, how much to test and how to focus on solving the business problem at hand.

References

Object Mentor Resources

jMock ooPSLA Article: Mock Roles, not Objects

Kent Beck: Just Ship Baby

Steve Freeman on Testing Smells: Listening to the Tests