1 to Many Without Breakage: One of TDD’s Best-Kept Secrets
TDD can become tricky when the next transformation step from red to green includes replacing a single element with a collection. I demonstrate how Parallel Change can help here as well.
Parallel Change is one of the most powerful refactoring techniques, but it has also another use: seamlessly moving from a single element to a collection of elements. This corresponds to the transformation step scalar → array in the Transformation Priority Premise (TPP).
Often when I moved from 1 to many using TDD, I found myself mindlessly ripping out old code and adding new code. Obviously, I often ended up in a state where my code did not compile anymore for quite some time!
All of a sudden it clicked - it’s the same problem that I had while refactoring and exchanging an existing abstraction with a another one. So Parallel Change should help me similarly as it does during refactoring, only this time I use it to move from a failing to a passing test.
The Example: Popping 2 Elements from a Stack
I take the example of a stack (which I borrow from Clean Craftsmanship by Uncle Bob), which shows exactly the case of moving from 1 element to a collection of elements. The relevant failing test (that you can also find here) reads as follows:
@Test
void pops_elements_in_the_reverse_order_of_how_they_were_pushed() {
var stack = new Stack();
stack.push(99);
stack.push(88);
assertEquals(88, stack.pop());
assertEquals(99, stack.pop());
}
This test currently fails because of the following code for a stack which only supports storing 1 element, so once 88 is pushed into the stack, 99 gets overwritten:
public class Stack {
private int size;
private int element; // only one element
public boolean isEmpty() {
return size == 0;
}
public void push(int element) {
this.element = element; // overrides element
size++;
}
public int pop() {
if (size == 0) {
throw new EmptyStackPoppedException();
}
size--;
return element;
}
public int size() {
return size;
}
}
Parallel Change Revisited
Let’s see how we can leverage Parallel Change to move this test from red to green by replacing the single element
with a List
. Here’s a brief summary of Parallel Change:
Add the new
List
close to theelement
to replace.For every time a value is assigned to
element
, add the same value to theList
.Switch all reads from
element
to an equivalent read operation from theList
.Remove all places where the
element
is assigned a value.Remove the
element
member variable.
Applying Parallel Change to Get to Green
In step 1, we add the List
right below the element
and run our tests. Only the test from above should still fail.
public class Stack {
private int size;
private int element;
private List<Integer> elements = new ArrayList<>(); // new
// ...
In step 2, we add a value to the List
wherever that value is assigned to element
. Here, it’s only once in the push
method. Still the same test should fail because only the state in element
and the List
are now the same, but we don’t access the list yet.
public void push(int element) {
this.element = element;
this.elements.add(element); // add this here
size++;
}
In the 3rd step, we’ll make the test green by replacing all read accesses to element
with a read access to elements
. We leverage the List
’s removeLast
in the pop
method. Finally, the test is green.
public int pop() {
if (size == 0) {
throw new EmptyStackPoppedException();
}
size--;
// previous: return element;
return elements.removeLast(); // new line
}
The step before would have shown us if we actually had the same state in both element
and the List
. We can now step-by-step remove occurrences where element
is assigned a value to (while running our test suite after each step):
public void push(int element) {
// remove this line: this.element = element;
this.elements.add(element);
size++;
}
And finally, we are ready to remove element
entirely from the stack, resulting in the following solution:
public class Stack {
private int size;
// removed: private int element;
private List<Integer> elements = new ArrayList<>();
public boolean isEmpty() {
return size == 0;
}
public void push(int element) {
this.elements.add(element);
size++;
}
public int pop() {
if (size == 0) {
throw new EmptyStackPoppedException();
}
size--;
return elements.removeLast();
}
public int size() {
return size;
}
}
This finalises the Parallel Change for the 1 to Many Transformation from element
to the List
, but we’re not yet done. We used Parallel Change to move from red to green, and now we enter the refactoring stage! And there is something to refactor: we still have the member variable size
that can now be removed, also using Parallel Change, so let’s apply it once again.
Parallel Change for Refactoring Revisited
We can skip steps 1 and 2 of the Parallel Change algorithm (achieving the same state in old and new structure) as we already solved the introduction of the new abstraction (elements
) and setting the same value as size
because every time an element is pushed to the list, it’s size automatically increases.
So instead, we can directly move to step 3 and replace all the read accesses to size
with a read access to elements
. Let’s start with Stack::isEmpty
:
public boolean isEmpty() {
// replaced: return size == 0;
return elements.isEmpty();
}
Then we move to pop
. Here we can use the improved Stack::isEmpty
method, which should have been used in the first place as it was a case of D.R.Y:
public int pop() {
if (isEmpty()) { // use the public Stack::isEmpty method here
throw new EmptyStackPoppedException();
}
size--;
return elements.removeLast();
}
And finally, let’s also adapt Stack::size
:
public int size() {
// replaced: return size;
return elements.size();
}
Now that we have switched all the read accesses to the List
, we can move to step 4 and remove all the places where size
is assigned a value:
public void push(int element) {
this.elements.add(element);
// remove: size++;
}
public int pop() {
if (isEmpty()) {
throw new EmptyStackPoppedException();
}
// remove: size--;
return elements.removeLast();
}
And finally, in step 5, we remove the size
member variable and are left with the following Stack
:
public class Stack {
// removed: private int size;
private List<Integer> elements = new ArrayList<>();
public boolean isEmpty() {
return elements.isEmpty();
}
public void push(int element) {
this.elements.add(element);
}
public int pop() {
if (isEmpty()) {
throw new EmptyStackPoppedException();
}
return elements.removeLast();
}
public int size() {
return elements.size();
}
}
… and we never broke a sweat.
Conclusion
This simple example illustrates how Parallel Change can both be used in TDD to solve the 1 to many transformation and seamlessly move from red to green, and also to replace an existing abstraction with another one in the refactoring phase.
Applied correctly, we never enter a state of non-compiling code and avoid Analysis Paralysis when we run into problems. I highly recommend you try out the example for yourself in the following repository (the solution can be found here). It’s a great tool to have in your TDD toolbox!
Have you applied Parallel Change in that manner before? Do you have other systematic strategies to move from 1 to many in TDD? Write them down in the comments so we can learn from one another!
If you like content like that, don’t forget to subscribe to stay up to date!
In our O’Reilly course we demonstrate a similar move from 1 to many using Parallel Change for extending a Clean Architecture Use Case from 1 Aggregate to a Repository of Aggregates, step-by-step using TDD. Check it out!
Want your team to learn how to develop evolvable software solutions? Visit our website www.codeartify.com and get in touch with us! We provide worldwide remote and on-site workshops for you and your team!
And lastly, if you are not yet part of our great community, check out our Tech Excellence Meetup and discuss with other likeminded people in Discord!