TDD and private methods

published: Mon, 5-Jan-2004   |   updated: Thu, 16-Jun-2005
Wells cathedral

Recently, I came across a problem when I was writing some code using TDD (Test-Driven Development).

The code I was writing was a fairly self-contained class, but it did use a number of private/protected methods, and a couple of hidden (private) classes as well. I was merrily writing test code, compiling, running, seeing red, refactoring, seeing green, when I suddenly came to a stop.

I had a private method that was doing something to help a couple of public methods. It had appeared when I was removing some duplicate code. Shouldn't I be writing tests for this private method as well? Shouldn't I be doing some Design by Contract work on it to see that it could cope with bad data? My mind started racing: how do I write tests for a private method? I came up with two possible answers.

The first was to temporarily make the private method public, and then write a couple of test cases for it. Yuk. When you refactor, you look for "code smells" and then refactor to remove the smells. A smell could be anything: duplicate code, a constant appearing in your code, one or more classes seeming to require an interface, a method becoming way too complex and long; the list is fairly long. Well, having to make private methods public in order to test them had a pong that would drive me from my desk.

The second was to use reflection to discover the private methods in my code and then run tests against them. That way my code would not have to change, but the tests would become inordinately complex and difficult to debug. Nevertheless, it sounded fairly cool: writing code to reflect over my compiled class, get the private methods, and then call them. The stink here wasn't as bad as plan A, but it was still pretty whiffy.

Couple that with the fact that I hadn't written any reflection code before (and, frankly, didn't want to start with this particular fairly simple class), and I took a couple of steps back.

What does TDD tell us? Essentially that you should write the test code first in order to get a failing test (in lots of cases, your failing test will be the code doesn't actually compile, let alone run), and then write the simplest code that makes the test pass. The final step is to refactor to remove those code smells, to make your code an clean and simple as possible. A corollary of this methodology for coding means that you should never be writing code that doesn't help you pass a test. (In other words, you shouldn't be thinking, gosh, I'm sure I'll need this particular method soon, so let's write it while I'm thinking about it. That thought process leads to code that hasn't been tested; that hasn't been written to fulfill a particular need, the need that's evidenced by the test case.)

Another more subtle corollary is this: since you are always writing or refactoring code to help you pass a test, you shouldn't worry about the fact that the private methods or classes you write have no explicit tests for them. They were written and are being used to help you pass the "real" tests of your system, the tests that check the proper functioning of your public members and classes.

So, guess what? If your private method fails because of some bad data, your normal tests should be picking it up anyway. If the private method can never be called with bad data from your other code, you shouldn't be altering or testing it to work with bad data. It's superfluous, a complete waste of time.

The conclusion you should draw from this discussion (as I did) is this: the tests you write define how your code will work. They define the public interface to your code. What your code does internally to fulfill the external contract defined by your tests is immaterial; your tests are checking to see that your entire code functions properly. So, if you have a class that relies on hidden classes to do its work, just test the public class. If you are writing an assembly that others will use, test the heck out of the published interface, don't bother testing the internal classes.