Week 11: Lab 7

Test-Driven Development

Objectives

This lab provides further practice with test-driven development, using a simple implementation of the list abstract data type.

0. Get the Code

All code for this exercise is contained in an IntelliJ workspace in Course GitLab repository.

Before you start working on this lab, make sure you update your Git repository to the latest version of the upstream code.

Using the command line (Terminal application on Mac/Linux, or Git-Bash on Windows) in the directory of your local comp6700-2017 repository, execute the following commands:

git pull git@gitlab.cecs.anu.edu.au:comp6700/comp6700-2017.git master
git checkout -b u1234567/comp6700-2017-master FETCH_HEAD

(as usual, don’t forget to replace the "u1234567" part in the git URL address with your actual university ID).

Review the changes if required; then merge the branch and fix any conflicts that come up:

git checkout master
git merge --no-ff u1234567/comp6700-2017-master

You can then open the IntelliJ workspace using File -> Open… and select the lab7 directory in your local Git repository.

1. Understand the Code

Your starting point is the BookList interface, as introduced in lecture A1. This interface defines an abstract data type that is very similar to the Java generic type java.util.List, however, it only operatates on instances of the Book class.

public class Book implements Comparable<Book> {
    //...
}

public interface BookList extends Iterable<Book> {
	public void addFirst(Book newBook);
	public boolean add(Book newBook);
	//public boolean remove(Book book); // not yet defined
	public void insert(Book newBook, int position);
	public Book get(int position);
	public boolean isEmpty();
	//public boolean contains(Book book); // not yet defined
	public int size();
	public String toString();
	public Iterator<Book> iterator();
}

In addition to BookList, there is a concrete implementation of the list ADT using a linked list, called BookListWithLL. Two of the methods on BookList are commented out; you will uncomment them and implement these methods in BookListWithLL later in the lab.

Most important of all, there is a test class TestBookList, which already contains a number of unit tests for the BookListWithLL implementation. As well as running the existing tests, you will create additional tests for new functionality.

In IntelliJ, run all four tests on TestBookList; they should all pass. Read the test code so you understand how it is testing each of the methods on BookListWithLL.

2. Add a New Test for Existing Code

The method BookList.insert(...) has already been implemented, but is not tested.
Create a new unit test TestBookList.testInsert() to ensure that the insert method is correctly implemented. When designing your unit test, carefully consider how insert changes the state of the list, for example, the position of the books that were already in the list.

Think about the pre-conditions, post-conditions and class invariants that should be true for the correct operation of the method.

A pre-condition is a condition that must be true about the state of the class or the parameters to the method before the method is run. Often it is not meaningful to directly test a pre-condition, but identifying the pre-conditions can help determine what should not be tested, e.g. if a pre-condition for a method is that a given parameter should not be null, then it does not make sense to test for correct operation of the method in the case that the parameter is null.

A post-condition is a condition that must be true after running the code in the method. The assertions you write in your unit tests will most commonly check post-conditions, i.e.

	myObject.someMethod();
	assert (/*post-condition for someMethod() */);

A class invariant is a condition that must always be true for a correctly constructed instance of the class. Unit tests also often include assertions to check class invariants, because (obviously) a class invariant must still be true after running the code in any method of that class.

3. Add a New Test for Yet-To-Be-Written Code

The method BookList.contains(...) has not yet been defined. (It is commented out in the interface definition.) Uncomment this method and write a skeleton implementation in BookListWithLL, for example:

    public boolean contains(Book book) {
        return false;
    }

Now write a new unit test, TestBookList.testContains(), to test your new method. Your new test should fail, because the skeleton code is not a correct implementation of the method. (This is the red phase of the ‘red-green-refactor’ cycle of test-driven development).

4. Write New Code to Make the Test Pass

Replace the skeleton code of BookListWithLL.contains(...) with a complete implementation, to make TestBookList.testContains() pass. (This is the green phase of TDD).

5. Refactor

There are three pieces of code in BookListWithLL which all perform the same basic function: stepping through the list from the beginning until the node at a particular position is reached. Look at the methods add(...), insert(...) and get(...) - you should notice that a portion of the code is essentially the same between these three methods. Refactor the class to extract this code into a new private method that returns the node at a given position, and replace these three pieces of replicated code with calls to the new private method. Ensure that all unit tests pass both before and after your changes to the code. (This is the refactor phase of TDD).

6. … and Repeat

(Optional) The remove() method is also not defined or implemented. Uncomment the BookList.remove() method, create a skeleton in BookListWithLL and create a new unit test. Then write a complete implementation of BookList.remove() so that your test passes.

Updated:  15 May 2017/ Responsible Officer:  Head of School/ Page Contact:  Alexei Khorev