Legacy Code to Testable Code #4: More Accessors!

Gil Zilberfeld points to one of the refactoring patterns in unit testing and unit tests that makes legacy code more testable.
Standard

This post is part of the “Legacy Code to Testable Code” series. In the series we’ll talk about making refactoring steps before writing unit tests for legacy code, and how they make our life easier. It continues the last post on accessors. Other posts include:

The Legacy Code To Testable Code Series

General patternsAccessibilityDealing with dependenciesAdvanced patterns
IntroductionAdd settersExtract methodStatic constructors (initializers)
RenamingMore accessorsExtract classMore static constructors
Add overloadIntroduce parameterInstance constructors
Testable objectConditionals to guard blocks

 

We talked about refactoring by adding “setter” accessors as a mean to inject values. The other side of the coin is when we want to know something has happened inside our object. For example, if internal state has changed, or a non-public method in legacy code was called.

In fact, this is like the “if a tree fell in the woods” question: Why should we care if internal state changed?

In legacy code, there are many cases where a class grows large and does many things. That’s why we’d think it needs refactoring in the first place.

If we had separated responsibilities, the result would be possible to unit test on another class. Alas, with god objects, things are a bit of a mess. When that happens, our acceptance criteria may be buried inside: We can either check internal state or internal method calls for its impact.

Refactoring time!

To check internal state, we can add a “getter” method. Adding a “getter” function is easy, and if it doesn’t have logic (and it shouldn’t), it can expose the information without any harm done. If the refactoring tool begs you to add a “setter” you can set it to be private, so no one else uses it.

In a funny way, “getter” methods can reverse roles: We can use a “getter” method to inject a value by mocking it.
So in our getAccount example:


By mocking the getBank method we can return a mockBank (according to our tools of choice):


On the other hand, we can verify a call on a “setter” instead of exposing a value. So if our Account object has an internal state called balance, instead of exposing it and checking it after the tested operation, we can add a “setter” method, and see if it was called.


In contrast to injection, when we probe we don’t want to expose an object on the stack. It’s in the middle of an operation, and therefore not interesting (and hard to examine). If there’s an actual case for that, we can use the “setter” method verification option.

In this example, the addMoney function calculates the interimBalance before setting the value back to currentBalance.


If we want to check the currentBalance before the calculation, we can modify the method to:


Then in our unit test we can use verification as a precondition:


Adding accessors is a solution for a problem that was created before we thought about unit tests: The design is not modular enough and has many responsibilities. It holds information inside it, and unit tests (and future clients) cannot access it. If we wrote it “right” the first time, the god class would probably have been written as a set of classes. With our unit tests in place, we want to get to a modular design.

Unit tests give us the safety to change the code. So are the automated refactoring tools. We can start the separation even before our unit tests using the Extract Method refactoring pattern.

We’re going to discuss it next.

Leave a Reply

Your email address will not be published. Required fields are marked *