Learning from Reading (and Rewriting) the Tests

[article]
Summary:

Automated unit tests verify that a component is working as expected.  They also serve as a way to understand how code works, though this doesn't always happen by reading tests.  Sometimes understanding comes from tweaking the tests to observe new failures, or rewriting the tests themselves. 

Automated unit tests verify that a component is working as expected.  They also serve as a way to understand how code works, though this doesn't always happen by reading tests.  Sometimes understanding comes from tweaking the tests to observe new failures, or rewriting the tests themselves. 

 

I was pairing with a colleague the other day and we needed to change a calculation object to use an additional input variable. We looked at the existing unit test code, scratched our heads trying to figure out what the tests were demonstrating, and refactored the tests to make them easier to understand. To do this, we applied little tweaks to make the code more readable, like replacing a line of code where we set up a mock invocation like this:

 

when(dateSource.currentDate()).thenReturn(Date.for("06/30/2009"));

 

To a method call:

 

todayIs("06/30/2009");

 

Under the hood, the todayIs method stubs a return value using exactly the same code as before, but it was easier for me to read and quickly understand.

 

We took a few more small steps to make the code easier to understand, using "Compose Method" (from Refactoring to Patterns) to make sure we had one level of abstraction per function (see Clean Code by Bob Martin, Chapter 3. Functions). When we finished, we felt the tests more clearly demonstrated the rules of the calculation.

 

As we talked about it, my colleague said a few things that I felt were thought provoking:

 

"The act of refactoring the tests really made me understand the code - more so than typically looking at the tests does." Intuitively this made sense to me - all the rapid feedback from tweaking an input and seeing a test fail or pass gave us a lot of information about how the algorithm worked. But I felt our refactored tests were very clear, and I hoped that a stranger to the code would be able to understand them fairly quickly. However, there's something to be said for different learning styles - some people can read something and understand it right away, while others need to "get their hands dirty" and experiment.

 

"I feel that tests are very subjective to the pair's preference and when they were written - and they change frequently." This struck a chord with me; look at all the new ways we have to test code in the last few years. Frameworks like cucumber and RSpec have created a formal syntax for how tests should read - the "Given/When/Then" syntax. This feels a bit like coding styles - each developer has their own opinions ("Braces at the end of the line or start of the line?") and tests are no exception (I've spoken with developers who loved or hated the RSpec syntax). If the team members have polar opposite preferences, I've seen a discussion of coding standards help bring everyone together - without creating an actual explicit standard.

 

"When you change the production code, you may have to rewrite the tests to focus on the area that is now most important." If the production code is changing significantly, then restructuring the tests to make sure the important details are easily visible doesn't feel to me like waste. I think the making the tests emphasize the important details and hide the unimportant ones is a good exercise and will pay off the next time you read those tests. However, many different test setups for a single class would make me start wondering if the class had too many responsibilities.

 

 

About the author

CMCrossroads is a TechWell community.

Through conferences, training, consulting, and online resources, TechWell helps you develop and deliver great software every day.