Test Objects, Not Methods

Posted July 16, 2009 by Industrial Logic

Black BoxI'm so often asked: "When microtesting, doing TDD, is it okay to test a private method?" The answer, at least literally speaking, is no! Let me explain. Imagine I begin test-driving a new object. As I consider my first test, I have to answer a design question: "What does this object do?" In other words, what can other objects ask of it and what should they expect to get or happen? I decide, then merrily go about writing tests to tease out the interesting details of this general behavior. Before long, I find something I need this object to do that complicates things. Complicates them to a point that tempts me to ponder, "Gee, I could just expose that new tricky private method by making it protected and test its logic directly. That sure would make things cleaner and simpler." And in one sense, I'd be right - that code deserves its own tests. But in a more important sense, I'd be wrong. Wrong in that what I should have realized is that this private method on Object A is really just begging to be the public method on a new Object B.
 
This private method really just wants to be a public method on another new object! Boom, I've just made a design decision! And wow, one that was driven out of just listening to my tests.
 
That method was private on Object A because it wasn't part of its "observable behavior." It wasn't "what the object does," it was an implementation detail of "how it does it." As soon as I started testing it directly, I had broken the fundamental rule of testing only the observable behavior of objects. I had started "testing methods" and, worse, a private one at that! Instead, I can and should remain true to the rule by making that logic the observable behavior of another object, one that my top-level object will then delegate to. Under the covers, interestingly, what's really happened is that I've paid homage to the Single Responsibility Principle. Upon making the decision about what that original object "does," I had established its responsibility, and embarked on encoding that into its tests. Once I wanted tests outside the scope of that established responsibility, I had found a new object, a new abstraction, asking for that behavior as its responsibility. This is primarily why the idea of microtesting being "white-box" testing so bothers me; microtesting is still "black-box" testing, and the box you shouldn't be looking inside of is the object. So, coming full circle, hopefully you'll see and agree how this boils down to a simple but powerful rule of thumb:
Keep yourself focused on writing tests for the desired behavior of your objects, rather than for the methods they contain.
When you can do this, you're sure to get more of TDD's real benefit - its power as a design tool!
  • pschwarz

    Back in 2009 I left a comment but it seems to have been deleted. So here it is again:

    Good post…here is what Rainsberger says in ‘JUnit Recipes – Practical Methods for Programmer Testing’:

    As you begin testing behaviour, rather than methods, your design will tend to show certain characteristics. In particular, there will be a public method for each operation, representing an “entry point” for exercising the behaviour. For a collection, these operations include add() and remove(). Behind this public method there may be non-public methods that the former invokes to help do its work. For a collection, these “helpers” may include resizing a dynamically sized collection when the underlying data structure runs out of space. This is part of the add() behaviour but not necessarily a method you expect the outside word to invoke.Typically you extract blocks of code and place them in private methods. There will come a time during the testing of a complex bit of behaviour that you will want to test one of those private methods on its own. If you want to write a test for that private method, the design may be telling you that the method does something more interesting than merely helping out the rest of the class’s public interface. Whatever that helper method does, it is complex enough to warrant its own test, so perhaps what you really have is a method that belongs to another class – a collaborator of the first. If you extract the smaller class from the larger one, the helper method you want to test becomes part of the newly extracted class’s public interface, so it is now “out in the open” and visible to the test you are trying to write. Moreover, by applying this refactoring, you have taken a class that had (at least) two independent responsibilities and split it into two classes, each with its own responsibility. This supports the Single Responsibility Principle of OO programming, as Bob Martin describes it.

    And here is Feathers in “Working Effectively with Legacy Code”

    “This question comes up over and over again from people new to unit testing: “How do I test private methods?”. Many people spend a lot of time trying to figure out how to get around this problem, but […] the real answer is that if you have the urge to test a private method, the method shouldn’t be private; if making the method public bothers you, chances are, it is because it is part of a separate responsibility: it should be on another class.”