Lab 1 - CS211

Test-Driven Development and Unit Tests

Introduction

This lab will introduce you to using Test-Driven Development (TDD) as a technique to write and develop your code. Additionally, it will provide you with a refresher to using conditionals, loops, and arrays. The reasoning behind the technique is simple: Before you write any code you have to know what it will accomplish. If you know what it will accomplish you know what input will produce what output. Given this information you write the code then try some test values out to make sure it works properly. The technique of TDD automates this process by having you first write automated unit tests that then help us design the code that needs to be written.

The process of TDD is as follows:

- FUNCTIONALITY: write a comment in the test code listing one thing you expect the eventual code you will write to do.

- RED: write a test and watch it fail

- GREEN: write the smallest amount of code that will make all of the tests pass

- REFACTOR: refactor the code (make the code elegant)

Following this pattern is very important because this structure gives TDD much of its power.

During the FUNCITIONALITY phase the engineer is determining what one small thing he/she wants accomplished next. This is helpful in the RED phase as it gets the engineer focused on one task only as it relates to the program and ensures the engineer understands that task.

During the RED phase, the engineer is structuring the code that is about to be written in his mind. You are figuring out the names of the classes, the names of the methods, and what you expect to happen based on key input values. This ensures that we aren’t coding randomly and slows the coding process down to make sure that the engineer thinks about the code before he writes it. Because this time is spent writing tests, it’s interesting to note that the engineer is focused on “how will the code be used” instead of “how to get the code to function.” This often means that the resulting classes have less complex interfaces.

During the GREEN phase, the engineer only writes the code necessary to make the test pass. This is where the engineer now has to care about creating the code for the needed functionality. This means that we should have 100% test coverage when we are finished. “Green” doesn’t just mean that the new test passes; it means that all tests pass. In this phase we are not only ensuring that we built what we needed, but also that we haven’t broken anything we had built before. This is another of the strengths of TDD, instead of having to retest a whole program by hand, the automated tests help make sure you don’t break anything that was working before. Imagine the time lost if you had to repeat all the tests by hand every time to make sure your program function properly.

The REFACTOR phase is critical to this process. Since the code is being developed

incrementally, it is critical that we continue to clean it up to reduce our design debt. For example, you just finished creating a new method to get a test to pass and realize the code for that method is very similar to code from another method. By taking the time then to better organize the code you can reduce the amount of duplicate code much easier than if you waited until the whole program is finished. In TDD, that refactoring is not an overhead process; it is integral to the development process and becomes second nature to the engineer. The engineer should be looking for every opportunity to refactor the code and, since these red/green/refactor increments are very small, these refactorings are not usually significant activities. Since TDD results in almost 100% test coverage, we can refactor with confidence because the tests ensure that we have not broken any behavior the system was expected to exhibit.

Applying TDD

For this lab you will be constructing a simple library that holds a small number of books which can be checked out by people who visit the library. You will be creating a program that models the diagram shown below:

Library Class – Keeps track of a small group of books that may be checked out. / Book Class - Keeps track of the name of the book, how many times the book has been checked out, and who has the book checked out. / Person Class - Keeps track of the name of the person and how books he/she has checked out.
Book[] theBooks – An array of books that may be checked out from the library. / String title - The name of the book.
Person whoHasMe - The person that has the book checked out, set to null if no one has the book checked out.
int timesCheckedOut - How many times the book has been checked out. / String name - The name of the person.
int booksCheckedOut - How many books the person has checked out.
Book checkOut(Person p, int index) – Checks out the book at index i in the array of Books to person p.
void checkIn(Book b) - Checks in the book to the library / void checkOut(Person p) - Checks out the book to person p.
void checkIn() - Checks the book back into the library.
String getTitle() - Returns the name of the book.
Person getClient() - Returns who has checked out the book. / String getName() - Returns the name of the person.
int getBooksCheckedOut() - Returns the number of books the person has checked out.

Diagram 1: Library Program Plan

The above diagram is a guide, and because we can't always anticipate everything we will need until we get there, so you need to keep in mind that modifying the initial plan is allowed.

Now we are ready to start coding. For the first part of this lab, most of the code will be given to you, so we won’t really need the REFACTOR step. However, as our systems get larger, we’ll see that stopping to look at our code and cleaning things up at each step will be very important. As we use this strategy, we’ll look at its effects on our development process and our code to evaluate its effectiveness.

Setting Up Our Tests

In Eclipse, create a new project to hold the files for this lab called Lab1.

I know you know how to do this, but we're going to set it up differently, so pay attention to these instructions. On the frame that lets you give the project a name, select "Create separate source and output folders." On the next screen, you'll see that it created a folder named “src” in your project. That's where the production source files will go. We also need a folder to hold your tests. Click "Create new source folder." Name that folder "tests." Click "Finish" and you've created your project.

The first class we’re going to create is the class that will hold the unit tests for Book: TestBook. To do this, right click on the package in the Package Explorer and select New->JUnit Test Case. At the top of the popup, select “New JUnit 4 Test” and give it the name TestBook. Near the bottom of the popup, you’ll see “Click here to add Junit 4 to the build path and open the build path dialog.” Click there and OK the popup window it causes. That is telling the compiler where to find the extra classes it needs to run JUnit. Click OK on the new test case popup and Eclipse will build the framework of your class.

Make your test class look like this:

import static org.junit.Assert.*;

import org.junit.Test;

/**

* Tests the functionality provided by the Book class

*

*/

public class TestBook

{

/**

* When a book is created, it should know its title, it

* should have been checked out zero times, and

* it should not currently be checked out.

*/

@Test

public void testInitialization()

{

Book book1;

book1 = new Book("Catch 22");

assertEquals("Catch 22", book1.getTitle());

assertEquals(0, book1.getNumberOfCheckOuts());

assertFalse(book1.isCheckedOut());

}

}

At this point, it won’t compile because we haven’t built the Book class, but there are some important things to notice:

First, the import statements at the top of the file are telling the compiler where to find the definition of specific JUnit classes that we need. They should not be giving you compilation errors.

Second, we have exactly one test and it is defined by the method named testInitialization. We know that method is a test because “@Test” precedes its declaration. That is called an annotation and gives the compiler and the JVM more information about that method.

Finally, here is a summary of what each step of the test is doing:

• declare a variable whose type is Book (this will hold one instance of the Book

class)

• create a book with the title “Catch 22” make book1 point to it.

• check to make sure that the book says that its title is “Catch 22”

• check to make sure that the book says that the number of times it has been

checked out is zero

• check to make sure that the book says that it is not checked out at this

time.

Notice that with our first test we have already started to make modifications to our initial plan. In this case we have decided we need a method isCheckedOut() for the Book class that wasn't part of the initial plan.

Strings

While we can declare classes for anything we need, Java also comes with many classes that are pre-defined. One of those classes is the String class. Each instance of String can hold a sequence of characters. We’ve actually used Strings in our previous labs without really knowing it. Every time we put characters in quotes in a System.out.println statement, that was a String. So, we already know how to pass Strings to methods (just put the text we want in the String in quotes).

The only other thing we need to know is that we can declare a variable to be an instance of String just like we have declared other variables:

String myString;

We can pass strings into methods just like we have passed them to println: just put quote around the sequence of characters.

Constructors

Often, instances require some code that initializes them. For example, if the instance contained an array, that might need to be allocated. In order to make our classes easy to use, we would like all of that initialization to be done in the “new” statement that allocates the instance.

Make the Test Compile

We are still working on the RED part of our mantra because that step requires that we run the test to see it fail. To do that, we’re going to need enough of the Book class to make the test compile. Use Eclipse to create the Book class. To build the code, let’s start by figuring out what it needs to know. In order to make the test pass, a book object would have to remember its title, so it needs an instance variable that is a String and basic definitions of the methods the test calls. Make your Book class look like this:

/**

* Functionality associated with Books. Each book has a title and

* knows about when it is checked out.

*/

public class Book

{

private String title;

/**

* Create an instance

*

* @param titleString the title of the book

*/

public Book(String titleString)

{

}

/**

* @return the title of the book

*/

public String getTitle()

{

return title;

}

/**

* @return the number of times this book has been checked

* out

*/

public int getNumberOfCheckOuts()

{

return 0;

}

/**

* @return true if the book is currently checked out

*/

public boolean isCheckedOut()

{

return false;

}

}

At this point, your test will compile and you can run it by right clicking on its name in the package explorer and selecting Run As->JUnit Test. You should see a new tab open where the package explorer is and it should look like this:

Diagram 2: JUnit Panel

It is showing that you ran TestBook, the red line shows it failed (RED), and it shows you the number of failures is 1. If you click on the triangle next to TestBook, it will show you the status of each test that was run.

Make the Test Pass

In order to make the test pass, we’re going to have to make the Book class store the value passed into the constructor into the title instance variable. As such make your constructor look like this:

public Book(String titleString)

{

title = titleString;

}

Re-run your tests and they should be GREEN.

As you should notice by now, in JUnit you have two main ways to test the correctness of a value: assertEquals and assertTrue/False. For assertTrue/False when the test fails you get the error message:

java.lang.AssertionError: null

In the case of assertEquals JUnit tells you the two values you are trying to compare. However, real numbers can be tricky to compare exact values for, so JUnit provides a way to set a range in which a value can fall:

assertEquals(x,y,0.15);

In this case the values x and y will pass the test so long as they are within 0.15 of each other. For example, if x = 0.5 and y = 0.6 then the test would pass.

Setting up Person

As always, we’re going to use TDD to develop our code. Remember: RED, GREEN,

REFACTOR. Start by creating a TestPerson class to hold your unit tests and make it look like this:

import static org.junit.Assert.*;

import org.junit.Test;

/**

* The test cases for the Person class

*

*/

public class TestPerson

{

/**

* At initialization, the person's name should be correct