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 patterns | Accessibility | Dealing with dependencies | Advanced patterns |
---|---|---|---|
Introduction | Add setters | Extract method | Static constructors (initializers) |
Renaming | More accessors | Extract class | More static constructors |
Add overload | Introduce parameter | Instance constructors | |
Testable object | Conditionals 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:
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 unit 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 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.
0 Comments