DEV Community

Emmanuel Genard
Emmanuel Genard

Posted on • Originally published at emmanuelgenard.com

Chapter 4: Privacy

Chapter 4: Privacy

Summary

This is a short chapter that cleans up after the refactoring in the previous chapter. Beck refactors the multiplication test to make it clear that the times method returns a new instance of the Dollar object.

He goes from this test

public void testMultiplication() {
Dollar five = new Dollar(5);
Dollar product = five.times(2);
assertEquals(10, product.amount);
product = five.times(3);
assertEquals(15, product.amount);
}

which checks against the primitive numbers 10 and 15.

To this test

public void testMultiplication() {
Dollar five = new Dollar(5);
assertEquals(new Dollar(10), five.times(2));
assertEquals(new Dollar(15), five.times(3));
}

which compares the result of the multiplication with new instances of Dollar.

This change is made possible because of the implementation of equals in Dollar. It also allows the instance variable amount to be made private since it is no longer referenced outside of Dollar.

Beck notes that this refactoring does a better job of expressing intent:

This test speaks to us more clearly, as if it were an assertion of truth, not a sequence of operations.

The rest of the chapter is about the tradeoff of TDD. He argues that TDD is not about perfect code but about having enough confidence with the code you've written to move forward.

Commentary

I think this chapter has two important ideas that are not talked about enough when discussing TDD. The refactoring of a test. The tradeoff of TDD.

The refactoring of tests is not emphasized in most of the writing about TDD. I've seen its importance in practice but there isn't much guidance on how to do it and when to do it. Beck does it here because the tests no longer express the intent of the code. This is a good heuristic but understanding when the tests no longer express the intent of the code is not always obvious. Other reasons I've refactored tests are, they don't provide any value, they are coupled to an implementation, or they are unreliable.

When tests are coupled to an implementation it is usually easy to spot. You write code that does the same thing in a different way but the tests fail. It can even be explicit duplication between the tests and the code. When tests are unreliable every test run is exciting. You don't know whether they will pass or fail even though you haven't changed any code. Value can be harder to determine. Sometimes the test tests something covered elsewhere. Sometimes it's the wrong test. Sometimes it's not important in the context of the domain. Sometimes the effort of maintaining the test is not worth it. These are a few of the things form my experience, I think there is a big opportunity in helping programmers evaluate and improve the quality of their tests.

The tradeoff of TDD is summarized beautifully by Beck.

By saying everything two ways—both as code and as tests—we hope to reduce our defects enough to move forward with confidence. From time to time our reasoning will fail us and a defect will slip through. When that happens, we learn our lesson about the test we should have written and move on.

It's not about preventing all mistakes. It's about lessening the chance mistakes. Like refactoring tests I think this is something that is under-emphasized in the discussions about TDD. It is not a silver bullet. I think "we hope to reduce our defects enough to move forward with confidence" should be mentioned anytime someone mentions TDD. The point is not to be good at TDD, the point is to write good software. TDD is a tool that can help you write good software.

An interesting question is what counts as "good" software?

Top comments (0)