Channels ▼
RSS

A Better Syntax For Unit Tests


Like most readers, I write lots of unit tests as I code. I don't do TDD, which requires writing the tests beforehand, but I often have two panes open in my IDE at the same time — one with the class I'm coding and one in which I'm banging out the corresponding unit tests. This allows me to test my code immediately when I save and build. Right away, I can tell that my code works correctly or contains off-by-one errors and other gremlins. I find this way of proceeding very agreeable. I have confidence in my code; and as I build up the large set of tests, I know I'm not inadvertently disrupting my previous work or that of my colleagues.

However, after years of working this way, I find that many tests are often hard to read. It's not the code itself (JUnit has done a good job over the years of improving syntax), it's that I often need to read the entire test through to understand what's being tested. Moreover, tests frequently have specific set-up requirements that can make up a significant portion of the test before the actual crucial line checks an assert statement. And that assert statement is often not terribly informative.

A final difficulty is JUnit's suboptimal syntax for supporting parameterized tests. These are the tests in which you send lots of values to the same routine to make sure it provides the right response under a variety of inputs. For example, if you are parsing regular expressions and testing the error detection, you'd want to send a large number of incorrect combinations and make sure all are properly rejected. With JUnit, such a test requires a lot of choreography.

So for a while, I've been looking at how to make my tests more readable and easier to write. The solution I've been moving towards is to use BDD tools. BDD (behavior-driven development) has at its core a series of tests that specify what behavior a program should exhibit when a certain action takes place. The many BDD frameworks in use today favor a syntax that has a flow that's roughly: Given a situation, when an action occurs, then a specified set of results should ensue. For example:

given a bank balance of $100
when a withdrawal of $150 is attempted
then an error should be displayed and the balance should be $100

I write lots of these tests, too. While I should embrace the entire BDD philosophy, which prescribes translating all requirements and user stories into tests of this kind, I instead write these tests just before I start in on a given piece of work. They are higher-level than unit tests and often span multiple units. Ideally, they incarnate some activity that would normally be part of UATs.

Most BDD frameworks ride on top of JUnit, which enables easy integration into innumerable IDEs and workflows. As a result, it is entirely possibly to write unit tests using this kind of syntax. Over the last year, I've been doing more of this. And as the readability soars, I find that I've essentially forgone the old, classic JUnit syntax.

In my case, because I like writing unit tests in Groovy, I use a Groovy-based framework, called Spock. (There are many other options including easyb and Cucumber. They all do roughly the same thing and use similar syntax.) The choice of Groovy to write unit tests for Java code is probably worth a separate editorial, but is one of the most productive decisions I've taken. Groovy has many useful features that simplify writing tests. The two that leap to mind are optional typing, which facilitates creating test objects, and method injection. Groovy's metaprogramming capabilities enable me to inject a new method into any object on the fly. Powerful stuff!

Returning to Spock, here's an example of a parameterized test:

class HelloSpock extends spock.lang.Specification {

    def "length of Spock's and his friends' names"() {
    expect: name.size() == length       
    where:  name     | length
            "Spock"  | 5       
            "Kirk"   | 4   
            "Scotty" | 6    
    }

}

Notice the test name is a string, which appears in any failure diagnostic. Already this is more readable than trying to use the JUnit convention of putting the explanation in an oversized Java method name. The test is clearly evident in the "expect" statement; the table of values in the "where" statement  is clear, understandable, and trivial to write. The result of this elegant and quick syntax is that I often go overboard and create a table with every possible edge value. In the above example, for example, I would surely test an empty string as well as one that consisted only of spaces.

More-complex tests are also elegantly supported. For example:

def "should send messages to all subscribers"() {
    when:
    publisher.send("hello")

    then:
    1 * subscriber.receive("hello")
    1 * subscriber2.receive("hello")
}

This test verifies that when a publisher sends a "hello" message, the two subscribers receive it exactly once. If they don't receive it or receive it more than once, the test fails. It's not hard to extend this snippet to express the expected behavior of mocks, is it?

I could go on; but the point is that, using this new syntax, my tests are far more readable, easier to write, and they invite me to include truly comprehensive testing at unit levels and higher.

— Andrew Binstock
Editor in Chief
alb@drdobbs.com
Twitter: platypusguy


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 


Video