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.