Introduction to Testing in Java: Writing Good Tests

Welcome to Part 3 of this review of the Pluralsight course Introduction to Testing in Java by Richard Warburton.

Richard is an empirical technologist and solver of deep-dive technical problems and works independently as a Software Engineer and Trainer. He’s the author of Java 8 Lambdas and a trainer on java8training.com.

He’s a leader in the London Java Community, has a set on the JCP and runs Java Hackdays, and has given talks at Devoxx, JavaOne, JFokus, Geecon, Oredev, JAX London, and Codemotion. He obtained a PhD in Computer Science from The University of Warwick.

Also in this series:
Part 1 – Introduction
Part 2 – Testing Code
Part 3 – Writing Good Tests
Part 4 – Test Driven Development
Part 5 – Dependencies

Writing Good Tests

The purpose of this module is to explain what good tests look like and how we can achieve them.

Good Tests

Richard begins with the important point that tests are a trade-off. They have many important benefits, but there are also costs associated with writing them.
He explains Maintenance, Readability and Coupling are all costs that we need to be aware of. Tests are code.

Good Practices

Good tests:

– Are Well Named
– Test the Behavior not the implementation
– Don’t repeat themselves
– Output useful diagnostics

Richard explains why each of these points are important. A good name provides executable documentation, makes maintenance easier and improves readability.

We see some naming anti-patterns to avoid, and then see some examples of good naming.

There’s lots of advice available on the Internet for good naming. Some rules that Richard likes best are:

– Use domain terminology
– Natural language
– Be descriptive

Behavior Not Implementation

Richard says this is one of the hardest things to get right, and I agree. We want to test the behaviour of a test but avoid testing it’s implementation.
We should avoid exposing private state for testing purposes.

And we want to design our tests in a way that allows us to change the implementation details without breaking our existing tests.

Also covered here is the Don’t Repeat Yourself principle. We can eliminate this by encapsulating it into common methods or using Before and After which will be explained soon.

Finally, avoid magic numbers!

Diagnostics

Ideally we don’t want to run up the debugger every time a test fails. The output of the test should make the problem quite clear to us.
We should prefer assertEquals over assertTrue when we are comparing numeric values because this gives better diagnostic information.

Better still we can add messages to our output. Richard gives the following example:

List order = cafe.brewOrder(...);
assertEquals("Wrong quantity of coffee", 1, order.size();

If this fails it will output:

Wrong quantity of coffee
Expected :1
Actual :0

Live Coding

In this lesson we see the test canBrewLatte failing with a unclear error message. Richard improves it so the output is better:

java.lang.AssertionError: Wrong coffee type
Expected :Latte
Actual :null

We see a similar improvement for the canBrewEspresso test.

Next, Richard highlights the duplication problem, and fixes it with an extract method refactor.

We also see how to turn the hardcoded number of beans, and other items, into constants.

Before & After

Code that commonly surrounds tests is for setting up and tearing down tests. To reduce or eliminate this duplication we can use Before and After annotations

@Before // Before each test method runs
@After // After each test method runs
@BeforeClass //Before all tests in the class
@AfterClass //After all tests in the class

Before & After (Live Coding)

Here we see how to use these annotations in practice. We start with simple methods that output messages to the console to describe themselves and see the order they run in is:

before class
before
after
before
after
before
after
before
after
before
after
after class

Then Richard shows us how we can use the before annotation in our CafeTest class.

Hamcrest

Hamcrest is a library for writing better tests by helping us avoid repetition and improve diagnostics.

It’s all based on matchers, which are blobs of logic used in assertions. If you’ve done unit testing in JavaScript before, you’ll probably have used matchers already.
In fact JUnit uses matchers as well.

Hamcrest (Live Coding)

We start by adding hamcrest-all as a project dependency. JUnit already ships with the Hamcrest core matchers, but to access all of the matchers we must add this.

With this added we can use assertThat. We can use powerful functions such as hasKeyhasItem and hasProperty as our arguments for this assertion.

Richard gives examples of using each of these.

Compositional matchers are matchers that take another matcher as a parameter.

assertThat(coffee, hasProperty("beans", equalTo(ESPRESSO_BEANS)));

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s