tdd

TDD – Add Second Test For Generic Code

In my last article on TDD, we continued building out the Add() method of the ShoppingCart class. We had cut down The Thorns, the error scenarios, and were laying our hands on The Gold, the happy path. To that end, we wrote a unit test adding 3 Apples to our ShoppingCart. Since Rule Three of TDD demands the simplest code passing the test, our implementation in Add() and the Total property contained much hard-coding:

Even after years of practising TDD, I still feel queasy hard-coding like this for the sake of passing a unit test. In this and many other instances, we know how the code will look when done. However, it’s best to curb the urge to jump in and complete the general algorithm without going through the TDD steps. Why? Because sometimes—no, often even—the code will turn out simpler when we follow the TDD process mechanically and to the letter.  

How do we get rid of the hard-coded values in our implementation code? Let’s recall Uncle Bob’s guidance:

“As the tests get more specific, the code gets more generic.”

What does that mean?

It means that we must add another unit test of the same shape but with different data pushing us to replace literal values with variable ones. Another specific unit test will drive us to write our code to work with generic data. 

Or, in a short formatting example, if test data { firstname : "Fred", lastname : "Flintstone" } produces output "Flintstone, Fred", then that same string literal, "Flintstone, Fred", can be the implementation. However, as soon as we call the same test with a second data point { firstname : "Barney", lastname : "Rubble" } we’re forced to generalise our formatting code to $"{lastname}, {firstname}". And now it will work for all firstname and lastname combinations. 

OK, let’s come back to our ShoppingCart example. We will need to add a second test with different, specific data to drive us to generalise the implementation code. Fine. Our existing test is for 3 Apples; so let’s add 5 Bananas:

 

This test fails:

 

The failing unit test tells us that we are adding Apples rather than Bananas. No surprise there when reviewing the code in Add():

 

Yes, that will not work for 5 Bananas. Time to generalise the ShoppingCartItem instance in Add():

 

We run our unit tests. There’s still a problem:

 

 

The Total property produces the wrong value. Let’s remind ourselves of the implementation for Total:

 

Yes, we’ll need to make that generic too. Here goes:

 

 

Are the tests passing now?

 

 

Yes! All the tests are green.

 

What have we learned today?

With the first specific unit test, we can often get away with a hard-coded implementation. But this is not what we want—we want the code to work in general, with all valid inputs. At this point, we require a second specific unit test with different data that pushes us to generalise our implementation code.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply