Legacy Code to Testable Code #4: More Accessors!
This post is part of the “Legacy Code to Testable Code” series. In the series we’ll talk about making refactoring steps before writing tests for legacy code, and how they make our life easier. It continues the last post on accessors.
We talked about “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 was called.
In fact, this is like the “if a tree fell in the woods” question: Why do we care if internal state changed?
In legacy code, there are many cases where a class grows large and does many things. If we had separated responsibilities, the result would be possible to assert on another class. Alas, with god objects, things are a bit of a mess. When that happens, our acceptance criteria may move inside: We can either check internal state or internal method calls for its impact.
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.
Role reversal
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:
protected Bank getBank() { return new Bank(); }public void getAccount() { Bank tempBank = getBank(); ...
By mocking the getBank method we can return a mockBank (according to our tools of choice):
when(testedObject.getBank()).thenReturn(mockBank);
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.
verify(account).setBalance(3);
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.
public void addMoney(int amount) { int interimBalance = currentBalance; interimBalance += amount; currentBalance = interimBalance; }
If we want to check the `currentBalance` before the calculation, we can modify the method to:
public void addMoney(int amount) { int interimBalance = setInterim(currentBalance); interimBalance += amount; currentBalance = interimBalance; }protected void setInterim (int balance){ return balance; }
Then in our test we can use verification as a precondition:
verify(account).setInterim(100);
Adding accessors is a solution for a problem that was created before we thought about tests: The design is not modular enough and has many responsibilities. It holds information inside it, and 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 tests in place, we want to get to a modular design.
Tests give us the safety to change the code. So are the automated refactoring tools. We can start the separation even before our tests using the Extract Method refactoring pattern.
We’re going to discuss it next.
Reference: | Legacy Code to Testable Code #4: More Accessors! from our JCG partner Gil Zilberfeld at the Geek Out of Water blog. |