Here is the first of seven blog posts based on the section in my book, Beyond Legacy Code: Nine Practices to Extend the Life (and Value) of Your Software, called Seven Strategies for Using Tests as Specifications. In this first post, I’ll discuss a technique called instrumentation that I learned from Scott Bain and Amir Kolsky at Net Objectives.
Instrumentation is a technique for writing your tests so that they’re more readable and understandable as specifications. We do this by replacing magic numbers and other values with constants or fields. This gives us an opportunity to assign meaningful names to the values we use to exercise the behaviors that we are testing, making our tests more readable.
For example, instead of doing this:
@Test
public void testConstructor() {
User user = new User(“Clark”, “Kent”, “user@example.com”,
“Superman”, “kryptonite”);
assertEquals(“Clark”, user.firstName());
assertEquals(“Kent”, user.lastName());
assertEquals(“user@example.com”, user.eMail());
assertEquals(“Superman”, user.userName());
assertEquals(“kryptonite”, user.password());
}
We do this:
@Test
public void testRetrievingParametersAfterConstruction() {
String firstName = “Clark”;
String lastName = “Kent”;
String eMail = “user@example.com”;
String userName = “Superman”;
String password = “kryptonite”;
User user = new User(firstName, lastName, eMail, userName, password);
assertEquals(firstName, user.firstName());
assertEquals(lastName, user.lastName());
assertEquals(eMail, user.eMail());
assertEquals(userName, user.userName());
assertEquals(password, user.password());
}
This makes the test more readable and disambiguates what it is we are actually specifying. In the first example, there are lots of redundant strings that the compiler can’t check for consistency, so if a field is misspelled, it can’t be caught by the compiler. It also makes it hard to understand the meaning of the fields with just the contents of the strings. The second example is clearer. It makes the test read like a specification so it’s clear what the code does. Instrumentation is one of my favorite techniques for helping me write clear and understandable unit tests that read like specifications.
I find that the discipline of thinking about the behaviors that I want to create before I think about how I want to implement those behaviors has helped me build better systems. As a result, the code I write is more partitioned and independently verifiable. I find that the technique of instrumenting my tests is valuable for making it clear what my code is doing.
As a result, I find that I have to write far less developer documentation because the way to use my APIs are how my tests consume them. With automated tests in place, I never have to run code through the debugger to verify that it works.
I find that one of the biggest challenges that developers face when adopting the test-first development methodology is knowing what tests are the correct tests to write. But if we think about our tests as a form of eliciting the behavior that we want to create, then it helps us build the right stuff and get immediate feedback that it works.
Instrumentation is a little extra effort but it gives me a great deal of value and allows me to turn an activity that I’m already doing, which is test-first development, into another activity—creating an executable specification for my code. I also get a suite of regression tests that validate that my features are written at the right level of abstraction to support me in refactoring my code later. To me, those are some pretty big benefits.
Note: This blog post is based on one of the “Seven Strategies…” sections in my book, Beyond Legacy Code: Nine Practices to Extend the Life (and Value) of Your Software.
Previous Post: « Why Practice 7: Specify Behaviors with Tests
Next Post: Use Helper Methods »