Here at Scoop, we believe that readability is a pillar of software development, and that extends to testing. So, when we set off to add unit testing to our Android app, we of course wanted to make sure our tests would be modular and readable, in addition to precise and robust. JUnit is the standard for unit testing Java, but the standard syntax is not that readable, nor that contextual. Luckily, a bunch of us here have Ruby experience, and Rspec is a great standard for Behavior Driven Development (BDD). So, we set out to make JUnit tests read more like Rspec. We were open to using a library, or even writing our own, but it turns out we didn’t have to. Instead, all we needed was a simple syntax change. Here’s what we came up with.

TL;DR

Use the HierarchicalTestRunner and some standardized naming to make your unit tests more modular and readable.

Problems with JUnit

First, what are the motivations for moving away from vanilla JUnit? Standard, best-practice JUnit suggests that each test case should build up and tear down its own state, and then make a single assertion. One can use helper methods to share code between tests, but each test case is assumed to be its own, monolithic entity, and should be analyzed and run as such. There’s no notion that tests can be grouped, except by file, and files often don’t have the desired granularity. Furthermore, if you use an @Before it can be hard to tell exactly what the state is under test. In contrast, when doing real world testing, we often want to build up state and then make a bunch of assertions about that state. Or better yet, change small parts of the state and make assertions about each change. Ideally, the code should communicate the state being tested, and group tests accordingly. Rspec handles these cases gracefully: the describe blocks allow us to organize related tests in a way that both groups similar tests cases and reuses the setup code, while keeping each individual test case as modular as possible.

A second point is that the naming conventions in JUnit are not as descriptive or fluent as RSpec. When you have a single assertion per test, the convention is to give a sentence-like name, starting with “test”. However, these names are often awkwardly long, and for blocks of similar tests, they are also repetitive. One workaround is to have more than one assert per test case, but that hurts the readability of the final output. It leads to vexing questions like, “which of the ten assertions in … failed?” Then you have to go look at the code, find the line number, figure out what’s going on, and then go take a break because you’re so fried. In summary, the describe/context/it or given/when/then is much, much more natural.

Comparing JUnit and Rspec

Let’s compare the differences between vanilla JUnit and RSpec, using a simple rock climber object. To determine whether the climber can climb certain routes, we want to know whether the climber has a rope or not. So, in this simple example, we’re testing the method hasRope().

public class ClimberTest {

private climber;

@Before

void setup() {

climber = new Climber();

}



@Test

public void testHasRopeWithRope() {

climber.setRope(new SterlingRope());

assertTrue(climber.hasRope());

}



@Test

public void testHasRopeNoRope() {

assertFalse(climber.hasRope());

}

}
describe('Climber')

describe('hasRope')

context('given a Sterling Rope')

it('should have a rope')

...

end

end

context('given no rope')

it ('should not have a rope')

...

end

end

end

end

A failing JUnit test would read something like:

java.lang.AssertionError
...
at com.example.ClimberTest.testHasRopeWithRope

While a failing RSpec test would read like:

Climber
hasRope
given a Sterling Rope
should have a rope

As you can see, the sentence-like output of RSpec is both more descriptive, and more pleasant to read.

Our (Simple) Solution

So, how did we move closer to the ideal of fluent, modular tests? We explored third party libraries, but didn’t find anything that was quite right. The closest we found was Lambda Behave, but it requires Java 8. In the absence of an acceptable library, we decided that it would be awesome if we did not have to spend the time to write our own. In essence, all we wanted was a way to reorganize and rename our current test methods in a sensical manner. So we did just that, using the HierarchicalContextRunner.

Update: JUnit 5 contains support for BDD with @Nested and @DisplayName. JUnit 5 targets Java 8 as well, but supposedly can be used with this plugin (I haven’t verified this yet).

Here’s an example of our solution, pulled from our code base. We’re testing a couple of methods on our TripRequest model, and each method has its own block of tests. We use inner classes to build the context for each class. The outermost class represents the class under test, and then each inner block is denoted by another class.

// Describe: the TripRequest model

@RunWith(HierarchicalContextRunner.class)

public class TripRequestTest {

static Instant now;

static Instant past;

static Instant future;



TimeSlot timeSlotNow;

TimeSlot timeSlotPast;

TimeSlot timeSlotFuture;



@BeforeClass

public static void setupClass() {

now = Instant.now();

past = now.minus(10, ChronoUnit.MINUTES);

future = now.plus(10, ChronoUnit.MINUTES);

}



// Create time slots

@Before

public void setup() {

timeSlotNow = MockTimeSlot.generateTimeSlot(now);

timeSlotPast = MockTimeSlot.generateTimeSlot(past);

timeSlotFuture = MockTimeSlot.generateTimeSlot(future);

}



// Context getFirstTimeSlot()

public class Method_getFirstTimeSlot {

TripRequest request;



@Test

public void givenNoTimeSlots_returnNull() {

request = new TripRequest();

assertThat(request.getFirstTimeSlot(), Matchers.nullValue());

}



@Test

public void givenOneTimeSlot_returnsTimeSlot() {

request = new TripRequest();

request.addTimeSlot(timeSlotNow);

assertEquals(request.getFirstTimeSlot(), timeSlotNow);

}



@Test

public void givenMultipleTimeSlots_returnsFirst() {

request = new TripRequest();

request.setTimeSlots(Lists.newArrayList(timeSlotNow, timeSlotPast, timeSlotFuture));



assertEquals(request.getFirstTimeSlot(), timeSlotPast);

}

}

}

A failing test looks like:

Note how fluently the failing test reads. It’s obvious what’s wrong, that when the method getFirstTimeSlot is given a single time slot, it’s not returning that time slot.

We have been really happy with this method, it facilitates tracking down failing tests, and greases maintaining our test suite, since all tests are clearly grouped. When we delete a method, we can just delete the whole block of methods. It’s not rocket science, but hey, it’s a really simple way to make tests cleaner!

Happy commuting!

— — —

Like this post? Want to see more behind-the-scenes articles about how we make carpools? Drop us a line @takescoop on Twitter OR add a comment here!

Categories: Engineering

Greg Lee

Greg Lee

Greg Lee was a Backend Engineer at Scoop, focusing on the engineering architecture and infrastructure serving Scoop carpoolers everywhere. In his spare time, Greg volunteers as a mobile developer and product manager for American Whitewater, a national nonprofit organization focused on the conservation, restoration, and safe enjoyment of America's whitewater resources.

Leave a Reply

Your email address will not be published. Required fields are marked *