I don't unit test my classes

Posted by: John Smart on August 30, 2010

I don't unit test my classes. I don't even unit-test my methods. You'll be hard-put to find the word "test" in my source code. And I never, ever create a new JUnit Test Case Eclipse.

I prefer to test how my application behaves. And I find it makes a huge difference.

So how does that work? Well, I usually start off with a user story. Or, more precisely, with an acceptance test criteria. But I could have started with detailed specifications - it doesn't really matter. The point is, I'm starting with some requirements - in other words, a description of what my code is supposed to do.

In practice, I will usually try to automate the acceptance criteria. Maybe a high-level test that steps through the whole user story to give a picture of where we are going. Automating the high-level test criteria, even incompletely, is a great idea, as it gives you a set of goal posts, an idea of what you are supposed to achieve for this requirement. It also forces you to think about the requirements in very concrete terms. I'll elaborate on this approach (which is known as Acceptance-Test-Driven-Development, or ATDD) in another article. I won't focus on domain modeling, or domain-driven-development, or high-level architecture workshops, or any other of the numerous technical activities that might end up going between the high-level requirements and the actual coding - all these have their place, but I don't want to discuss them here. In this article, I want to focus on the more low-level tests.

I start off the actual coding by creating a new test class. But I don't call this class AccountTest or GizmoTest, or any other name with the word "Test" in it for that matter. I prefer to name my test after the feature I am trying to implement: WhenAnAccountManagerConsultsTheClientDetails WhenAClientTransfersMoneyBetweenAccounts, or WhenTheGizmoSpinsInAClockwiseDirection. I might need several such test classes to fully explore the feature I'm working on. And I might need to drill down, later on, into more technical details. But the essential approach is this: it's all about providing context.

This is by no means just a formality. Naming the class well forces me to think about the feature I'm trying to implement. And it's harder than it sounds. But it gets me thinking about what the application will do in practice, and is the first step in the detailed design process.

Next, I drill down into the details. What exactly should the code do? I typically express this in the form sentence-like structures, very similar to the acceptance criteria. If I need to, I'll add some extra ones to clarify the requirements further. This is pretty standard TDD-stuff - nothing fancy here. The important thing is to make sure each one contains a context, an action and an expected outcome:

  • When the account manager clicks on the client name the client details page should be displayed
  • When the source account has insufficient funds, the transfer is refused and no money is deducted from the source account
  • When the gizmo reaches 10 RPS, the red flashing light goes on.
  • ...
Sometimes the name of the class acts as a context, so you can get away with just the action and the expected outcome. But there should be an action and an expected outcome expressed somewhere.

Each of these sentences becomes a test. The exact form of the test will depend on your project. For Java projects, JUnit 4 will work fine, as will nUnit for .Net code. For the higher-level tests I am more inclined to use a dedicated BDD framework such as easyb or JBehave. For Groovy and Grails, Spock is a great developer-focused BDD tool. And Ruby developers are very fond of RSpec and Cucumber. But even if you don't want to open a new tool box, you can still take advantage of this approach - just pay attention to your test names!

In JUnit 4 code, for example, the tests might look something like this: public void whenTheUserClicksOnTheClientNameTheDetailsPageShouldAppear(){ ... } or public void whenTheSourceAccountHasInsufficientFundsTheTranferIsNotDone() { ... }

And now that I have a good idea about what I am supposed to be coding, I can start to think about classes and methods. Indeed, how could I start to code, if I don't understand the problem?

With a bit of care, this approach also give you a clean, well-organized, and above all working set of examples covering in detail how the different features that make up the application have been implemented, and how classes used to build the application are meant to be used. The class names and the method names read like a narrative. All you need to do is to organize them into sensible packages (for example, by feature).

So I'm not actually testing my classes, or my methods. I'm testing the behaviour of my application - I just happen to be doing it by exercising classes and method calls. And the same approach applies at any level - I could be implementing web tests using page objects, or writing a low-level technical module. The same principles apply.

This approach is of course not new - it's known as Behaviour-Driven Development, and there are some great tools out there to help you formalize the process. No matter what tool or language you are using, I would recommend 'The Rspec Book', by Dan North and friends, as a good introduction to the general approach.

John Smart

About John Smart

John is an experienced consultant and trainer specialising in Enterprise Java, Web Development, and Open Source technologies, currently based in Wellington, New Zealand. Well known in the Java community for his many published articles, and as author of Java Power Tools, John helps organisations around the world to optimize their Java development processes and infrastructures and provides training and mentoring in open source technologies, SDLC tools, and agile development processes.