Skip to main content
Blog

The Librarian: Introduction to Test-Driven Development

By 25 juni 2016januari 30th, 2017No Comments

This will be a series of articles revolving around unit testing where I will work through examples and exploring various aspects of the craft. This is the first installment.

This post is a crosspost from my personal blog. The code associated with this article can be found on GitHub. Future and past installments can be found in The Librarian Archive.

TDDI will try to implement a few requirements for a Library module with books and memberships, extending whatever code we have in a test-driven style (“TDD”) as we go along. I share a few thoughts about the process, show some refactorings and give a few hints for using the IDE.

The level of this article is for junior developers who want to expand their testing horizon.

There’s plenty of information out there which describes what TDD or Test-Driven Development is, the red-green-refactor cycle etc so I won’t delve into too much introductory detail here. See the references at the end for more background-information.

Instead, just get started!

We’re starting out with an Gradle project with the following build.gradle file:

1
2
3
4
5
6
7
8
9
apply plugin: 'java'
 
repositories {
    jcenter()
}
 
dependencies {
    testCompile 'junit:junit:4.12'
}

This states we’re in a clean Java project, can ceate our tests in src/test/java and sources in src/main/java and we have a dependency on JUnit – our testing framework of choice. I could have chosen TestNG or Spock, but that’s a topic of another post.

First story in micro-steps

We’re driving the code by tests and we’re driving the tests by requirements. That’s the cycle we’re repeating.

If we were in an agile project requirements might come in the form of a user story like:

As a librarian,
I want book-loving persons to become members of the library
So that I can lend out books to them in the future

We can identify a few concepts here: a library, persons, (becoming a) member, (lending) books. Let’s concentrate on, what seems, our central concept: the Library.

So, you might be tempted to dive in and create e.g. a Library class and code away, but we shall do no such thing!

Make a failing test

We’ll start with a failing test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package example;
 
import static org.junit.Assert.*;
 
import org.junit.Test;
 
public class LibraryTest {
 
    @Test
    public void test() {
        fail("Not yet implemented");
    }
 
}

A public method called test(), annotated with JUnit’s @Test annotation. You can see a staticly imported method fail() here giving us a solid, failure when we run the LibraryTest class — either with our IDE or through Gradle.

1
2
3
4
java.lang.AssertionError: Not yet implemented
    at org.junit.Assert.fail(Assert.java:88)
    at example.LibraryTest.test(LibraryTest.java:11)
    <snip>

Intention-Revealing Name

We’re going to rename the method to a more suitable test name which reveals the intention of our first test, which is testing that members should be able to get registered.

Let’call it something like shouldRegisterMembers().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package example;
 
import static org.junit.Assert.*;
 
import org.junit.Test;
 
public class LibraryTest {
 
    @Test
    public void shouldRegisterMembers() {
        fail("Not yet implemented");
    }
 
}

(Yes, you can run it, but it will still fail)

Ok, but now what? I don’t have any code to invoke

Well yeah. We’re going to change that.

By a series of well-executed refactorings, hopefully performed by your IDE, we will create just-enough code to pass the test. By doing so, we will create our production code; the code for a Library class which does not exist yet. The rule for now is: if there is no test requiring a piece of production code, we will not write it.

We need a Library class to register members for. So I’m writing down the only code I need at this moment.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package example;
 
import static org.junit.Assert.*;
 
import org.junit.Test;
 
public class LibraryTest {
 
    @Test
    public void shouldRegisterMembers() {
 
        // given
        Library library = new Library();
    }
 
}

If you are in an IDE it will signal something like “Library can not be resolved to a type”. Yes, that’s your friendly compiler being a helpful fellow. You need to fix this by creating the class, or have the IDE create it for you.

In e.g. Eclipse you can choose a Quick Fix, called “Create class Library”.

Use the IDE, Luke!

Performing code modifications, such as creating missing classes or methods, are no-brainers for modern IDEs and I highly recommended you always use them for these kind of tasks.

Our newly, created Library looks like

1
2
3
4
package example;
 
public class Library {
}

which is all we need to compile the test class and run the test. And pass.

A test which just instantiates a new Library — which doesn’t do anything — isn’t adding value yet. It’s needed because we need to create our logic from the user story: “registering members”.

What better way to express this a method call named: registerMember? We don’t know much about a member yet, but for now I am giving him or her a name – a simple String to be passed along to identify a member.

I need to talk to my librarian later on to clarify all the properties we expect a member to have.

So, we’re registering a member, providing an example value of “Ted”. The code will now look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LibraryTest {
 
    @Test
    public void shouldRegisterMembers() {
 
        // given
        Library library = new Library();
 
        // when
        library.registerMember("Ted");
    }
 
}

The LibraryTest again does not compile anymore. The compiler will complain:

The method registerMember(String) is undefined for the type Library

Create the missing method

Use the IDE to create this method in the Library class. Not much to look at eh? Now, we’re going to do something new: add some Javadoc to it, describing what it does.

Still not much, but the LibraryTest compiles again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package example;
 
public class Library {
 
    /**
     * Registers a new member using provided name.
     *
     * @param name
     *            The name of the member
     */
    public void registerMember(String name) {
    }
 
}

The test also passes.

Since we’re still not convinced we’ve implemented our logic (since we haven’t implemented anything yet really) let us design things in such a way that, if registering a member went successful, the library will give us a new, full-fledged Member back.

We adjust the test like this: have the registerMember method return the successfully created Member, so we possibly can check whether the member’s name equals the one we provided.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class LibraryTest {
 
    @Test
    public void shouldRegisterMembers() {
 
        // given
        Library library = new Library();
 
        // when
        Member newMember = library.registerMember("Ted");
 
        // then check for member's name to be same
    }
 
}

That’s right, the registerMember method at the moment returns void: if we want a Member we’ll have to change the method’s return type for the test to compile again. In order to do that, we’ll first have to create a Member class because that doesn’t exist yet either.

1
2
3
public class Member {
 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Library {
 
    /**
     * Registers a new member using provided name.
     *
     * @param name
     *            The name of the member
     * @return
     */
    public Member registerMember(String name) {
    }
 
}

Can we actually keep changing and creating stuff like this – driven by the tests?

Yes.

Almost there. If we want to check the name of the member we can use Hamcrest matchers equalTo and is with a yet-to-be-made getName() — to compare the name “Ted” from the input with the one in Member.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package example;
 
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
 
import org.junit.Test;
 
public class LibraryTest {
 
    @Test
    public void shouldRegisterMembers() {
 
        // given
        Library library = new Library();
 
        // when
        Member newMember = library.registerMember("Ted");
 
        // then
        assertThat(newMember.getName(), is(equalTo("Ted")));
    }
 
}

Now what do we need to pass the test?

  • To compile, the Member class will need a getter, called getName(). By convention, but also needed by the code we will put in registerMember, make name a final property (immutable, no setter) which we’ll initialize by a constructor. Execute a double-whammy and make this so:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Member {
 
    private final String name;
 
    public Member(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
}
  • Create a Member and return it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Library {
 
    /**
     * Registers a new member using provided name.
     *
     * @param name
     *            The name of the member
     * @return registered member
     */
    public Member registerMember(String name) {
        return new Member(name);
    }
 
}

Phew!

The test passes.

It looks like we have fulfilled our requirement as per user story, which was

As a librarian,
I want book-loving persons to become members of the library
So that I can lend out books to them in the future

Is the requirement fulfilled?

As a small side-note: I’m looking at the story and I’m seeing “members”, plural. As my test is testing the library for just one member, I’m taking the liberty to beef things up and test for more members.

The minimum amount of member registrations tested — I need to get enough confident about the library supporting “members” (plural) — is two, right?

Modified the test a bit to include Ted and Bob now.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package example;
 
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
 
import org.junit.Test;
 
public class LibraryTest {
 
    @Test
    public void shouldRegisterMembers() {
 
        // given
        Library library = new Library();
 
        // when
        Member newMember1 = library.registerMember("Ted");
        Member newMember2 = library.registerMember("Bob");       
 
        // then
        assertThat(newMember1.getName(), is(equalTo("Ted")));
        assertThat(newMember2.getName(), is(equalTo("Bob")));
    }
 
}

Test passes.

2nd story – somewhat faster

Let’s implement a 2nd user story. It goes like this:

As an accountant,
I want a person being able to register for a membership just once
So that I don’t end up having multiple memberships for the same person

When you look at it, the first user story implemented in code isn’t really a big deal. We haven’t gold-plated anything, we don’t have anything there we haven’t tested. You know the drill of make-a-failing-test, fix and repeat.

Bit of design

Let’s create a new test called shouldNotRegisterAgainWhenAlreadyMember – clever huh?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package example;
 
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
 
import org.junit.Test;
 
public class LibraryTest {
 
    @Test
    public void shouldRegisterMembers() { ... }
 
    @Test
    public void shouldNotRegisterAgainWhenAlreadyMember() {
 
        // given
        Library library = new Library();
        library.registerMember("Ted");
 
        // when we register with same name
        library.registerMember("Ted");
 
        // then we should see it fail somehow
    }
}

This is where a bit of design comes in. How can we have the code tell us a member by the same name is already registered?

If this normally would not be possible but we still have to deal with these kind of situations, use of Java’s exception mechanism could be a candidate.

We’ll create an AlreadyMemberException – which the method registerMember() can throw to indicate this exceptional event.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class LibraryTest {
 
    @Test
    public void shouldRegisterMembers() { ... }
 
    @Test
    public void shouldNotRegisterAgainWhenAlreadyMember() {
 
        // given
        Library library = new Library();
        library.registerMember("Ted");
 
        // when we register with same name
        try {
            library.registerMember("Ted");
            fail("should not have registered Ted twice");
        } catch (AlreadyMemberException e) {
            // success!
        }
 
    }
}

What happens?

  • As part of the setup of the test (under //given), the library starts out with an existing member with the name “Ted”
  • When the library is told (“don’t ask”) to register another member for the same name “Ted”, we expect an AlreadyMemberException to be thrown: the Library shouldn’t allow multiple members with the same name
  • If registerMember("Ted") does not throw the exception, we’ll fail the test on fail()
  • There might be a more elegant way of expecting a certain exception to be thrown, but we won’t want to get ahead of ourselves:-)

Right now the test fails – NO exception is thrown since we haven’t updated the Library yet.

Let’s do that now.

The code should somehow now track members internally, else it wouldn’t be able to remember members registered between invocations.

The simplest solution (KISS) is using a data structure from the Collections Framework for that. Any Collection, such as ArrayList, has methods such as contains (to check for presence) and add – to fulfill all our needs:

  • check for a member
  • add a member

Our solution would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package example;
 
import java.util.ArrayList;
import java.util.Collection;
 
public class Library {
 
    private final Collection<Member> members = new ArrayList<>();
 
    /**
     * Registers a new member using provided name.
     *
     * @param name
     *            The name of the member
     * @return registered member
     */
    public Member registerMember(String name) {
 
        Member newMember = new Member(name);
 
        if (members.contains(newMember)) {
            throw new AlreadyMemberException();
        }
 
        members.add(newMember);
        return newMember;
    }
 
}

Run the our test method and… see it still fail. WAT?

Know your framework

Nobody is throwing an exception, but the code is simple enough to expect that contains and adding should Just Work. Is it a flaw in the test itself?

No, one has to know to when putting objects like Member in a Collection and we want the Collection check for equality (and not identity) we need to implement equals() and hashCode() in Member.

You could generate these methods with any decent IDE (or invoke helper methods from helper frameworks for doing this) but code below (generated by Eclipse) will suffice:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package example;
 
public class Member {
 
    private final String name;
 
    public Member(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Member other = (Member) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
 
}

Test passes!

Refactor

Now that we have some test coverage, we can do something we left out up until now: refactor!

The rules are:

  1. Make it work
  2. Make it better (faster etc)
  3. Make it readable (DRY, maintainable etc)

We made it work. By the process of refactoring – which often is described as “series of small behavior preserving transformations” we can tackle the remaining two bullets: make it “better” and readable if this is possible. And this is always possible: the pitfalls I’ve encountered over the years is that one can refactor til infinity – knowing when to stop is often tricky.:-)

These are just examples, but we could…

Simplify the implementation

We can probably make our existence-check simpler and get rid of contains and use the return value of add directly — if we convert to a Collection-type which prevents duplicates by itself.

We can probably replace ArrayList by HashSet and add will return false if the collection of members won’t allow adding a member it already has. This would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package example;
 
import java.util.Collection;
import java.util.HashSet;
 
public class Library {
 
    private final Collection<Member> members = new HashSet<>();
 
    /**
     * Registers a new member using provided name. [...]
     */
    public Member registerMember(String name) {
 
        Member newMember = new Member(name);
 
        if (!members.add(newMember)) {
            throw new AlreadyMemberException();
        }
 
        return newMember;
    }
 
}

Simplify the tests

Ha, you thought only the implementation would need work? No, also the tests are each iteration of test-fix-refactor eligible for polishing up.

E.g. if we look at the the part which is the same in each testmethod in LibraryTest it’s the instantiation of a new Library().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LibraryTest {
 
    @Test
    public void shouldRegisterMembers() {
 
        // given
        Library library = new Library();
        ...
    }
 
    @Test
    public void shouldNotRegisterAgainWhenAlreadyMember() {
 
        // given
        Library library = new Library();
        ...
 
    }
}

We can apply a refactoring called Convert Local Variable to Field and initialize our field before each test, using JUnit’s @Before annotation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class LibraryTest {
 
    private Library library;
 
    @Before
    public void setUp() {
        library = new Library();
    }
 
    @Test
    public void shouldRegisterMembers() {
 
        // when
        Member newMember1 = library.registerMember("Ted");
        Member newMember2 = library.registerMember("Bob");       
 
        ...
    }
 
    @Test
    public void shouldNotRegisterAgainWhenAlreadyMember() {
 
        // given
        library.registerMember("Ted");
 
        ...
 
    }
}

Tests pass!

(You we’re probably wondering when I would get around to it :-))
And last, but not least – there is a way to simplify expecting a certain exception with JUnit: the ExpectedException rule — which really gets rid of the clutter of our last test.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package example;
 
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
 
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
 
public class LibraryTest {
 
    @Rule
    public ExpectedException thrown = ExpectedException.none();
 
    private Library library;
 
    @Before
    public void setUp() {
        library = new Library();
    }
 
    @Test
    public void shouldRegisterMembers() { ... }
 
    @Test
    public void shouldNotRegisterAgainWhenAlreadyMember() {
 
        // given
        library.registerMember("Ted");
 
        // fail when we register with same name
        thrown.expect(AlreadyMemberException.class);
        library.registerMember("Ted");
 
    }
}

Tests pass!

In conclusion

I agree with some conclusions made by James Shore in an article (“How Does TDD Affect Design”) many, many years ago: TDD can lead to better design, TDD can lead to worse design. The TDD perspective is just one of many in the testing space and be a welcome addition in anyone’s tool-belt. I’d like to believe we haven’t created any production code which has not been tested, but I didn’t run any code coverage tool yet to verify if I have covered all the paths – but that wasn’t the aim for now either way:-)

I hope the article showed a glimpse of how the TDD cycle can work and how to guide the design of our classes with our tests and end up with regression test suite as a great side-effect.

References