Extreme Teaching: Introducing Objects
Often, the people who spend their days doing object-oriented programming don't communicate with those who spend their days teaching object-oriented programming. It means that innovations on each side of this divide are often not shared with the other. Recently, the FIT framework has been gaining popularity as an acceptance-testing framework. FitNesse is a wiki-based wrapper for FIT that adds more functionality.
Together, these applications allow a teacher to write an executable assignment. In other words, the teacher can specify what the assignment is to do, in such a way that the student, faculty member, or grader can run this spec against the students' efforts throughout the development phase. This means that the spec itself can also help the student make progress by informing the student of what needs to be done next. A student can work through an assignment knowing which parts have been satisfied at any moment.
This article introduces a basic example of how you might use acceptance testing to lead students through an assignment. We'll begin with a rant against "Hello World," continue with a quick introduction to FitNesse, and conclude with the tables and fixture for running this example.
A First Object-Oriented Program
The "Hello World" program is traditionally the first code that a CS 1 student compiles and runs. The program looks something like this.
public class HelloWorld {
public static void main(String [] args){
System.out.println("Hello, world.");
}
}
The student saves this as HelloWorld.java, compiles the source
code into a class file, HelloWorld.class, and runs HelloWorld. After
all of this typing, the student is rewarded by seeing "Hello,
world." in the console window.
I've written elsewhere
about the problems with starting a course on object-oriented
programming with this non-object-oriented program. There are no
objects anywhere. Almost everything happens inside of the
main() method. How do you treat this initially confusing
main() method? Do you explain the components of its
signature first, or do you tell the students to just type it in and
say you'll explain it later? The first option requires you to explain
static methods in a class that contains no other methods, a return
type for a method that doesn't return anything, access levels for a
method that doesn't restrict access in any way, method parameters in a
method that never uses the args variable, and arrays of
String objects that are never set.
In the talkback to my original ONJava article, one reader pointed out that "Hello World" was the smallest possible program that a student can write and have working code that provides feedback to the user. In a way, it is a verification that their development environment is set up right and that they know how to save code to the right spot and then compile and run it.
In the followup article, I proposed the following first assignment for students. They have to have to write and compile the code that makes the following two lines work.
Friend friend = new Friend();
String yourName = friend.getName();
// somehow I will display yourName once I have it
Already we can have a rich discussion. The rest of the article used a custom class loader to lead the student through the process of:
- Creating a file named Friend.java and saving it in the appropriate directory.
- Stubbing out the class skeleton:
public class Friend{}. - Compiling the file Friend.java into Friend.java.
- Adding the
getName()method that returns aString.
The result is a version of Friend.java that looks something like this.
public class Friend {
public String getName(){
return "Daniel";
}
}
The students have still written a very short piece of code and had
to learn how to save code, compile, and run it. They have also learned
more. They weren't given the code they had to write -- they had to
figure it out from the clues in the client code. The client code
specifies that it will need a class named Friend with a
method named getName(), with certain expected
results.
Getting FitNesse Up and Running
The obstacle to using the approach detailed in the second article was that it required quite a bit of coding to make the custom class loader provide the appropriate feedback to lead a new user through the project. The FIT framework was designed by Ward Cunningham to allow customers to use simple HTML tables to specify the expected behavior of an application. The customer would write the tables, the developer would write the code, and the developers would also write a little bit of glue code to tie the two together. Much of Ward's work has been captured by others in the practices and principles of Extreme Programming(XP). Although I do advocate using Test-Driven Design and other aspects of XP in the classroom, FIT and the techniques described in this article can be used whether or not you intend to do so.
The remainder of this article will show how you can use the FIT framework in the classroom to specify and monitor an assignment. In an intro course, the instructor can specify the requirements in tables and the students can write the code that makes the tests pass. Initially, the instructors will need to write the glue code. These so-called acceptance tests are not the same as unit tests and are meant to supplement the unit testing that a student does. Unit tests, however, are outside of the scope of this article.
Bob and Micah Martin have open-sourced FitNesse, a wiki-based front end for FIT that is written in Java. In this article, you use FitNesse as an engine for driving the student assignment. Download the latest version of FitNesse and unzip it. The fitnesse directory contains two run files: run.bat for Windows and run.sh for Unix. You can also run using fitnesse.jar from the command line as follows:
java -cp fitnesse.jar: fitnesse.Fitnesse -p 8099
Here I've used the extra flag -p 8099 to use port
8099 instead of the default port 80. If you are already using port 80
to serve web pages, you will want to select a different port. Other
command line arguments, documented in the User Guide, let you
specify the location of the top-level pages for the site being served
and whether or not logging is turned on. Open a browser and enter the
URL:
http://localhost:8099
If you used the default port, you do not need to include the port
information. If you chose another port, modify the URL
accordingly. The instructions in this tutorial will assume the port is
8099. You should be redirected to http://localhost:8099/FrontPage and be greeted by
"Welcome to FitNesse". Follow the link to FitNesse.UserGuide. If you
are not familiar with wikis, you can find instructions here. You can
also find reference material for writing and running acceptance
tests. Click on the link to RunningAcceptanceTests.
Note: Before going further, please check to see if you are
running the July 28 release of FitNesse. You can determine this by
looking at the .zip file, which is named
fitnesseyearmonthdate.zip. If your file is fitnesse20030728.zip,
you will need to take the following steps to clean up your FitNesse
installation. Go to the URL http://localhost:8099/ClassPath. You should see this:

The first and third lines need to be removed. Click on the "Edit" link in the left column. You'll be taken to an editor that shows this raw text used to generate the wiki page.
!path c:\javalib\XPWDC Example
!path fitnesse/classes
!path c:\fit\Release\fit.jar
Comment out the first and third lines by putting # at
the beginning of each line. You should now have this:
#!path c:\javalib\XPWDC Example
!path fitnesse/classes
#!path c:\fit\Release\fit.jar
Press the Save button and you should be taken back to the ClassPath page with the first and third classpath lines now gone.
Go to http://localhost:8099/FitNesse.RunningAcceptanceTests. At the
top of the left column you should see the link "Test". Click on it, and after a moment the top of the table
should turn yellow and you should see something like Figure 1.

Figure 1. ClassNotFound when running acceptance test
You need to add eg.Division to the classpath. If you look around
the FitNesse distribution, you will find eg inside of FitNesseRoot/files/examples. Click on the "Edit" link at the top left. Add the line !path FitNesseRoot/files/examples/ below the table, as is highlighted below.
Acceptance tests are run by hitting the '''Test''' button (or
typing ''ALT-t''). This button appears on any page that has the
'''Test''' attribute set. See [[Page
Attributes][MarkupPageAttributes]].
Hitting the '''Test''' button is equivalent to using the
following command:{{{!r fitnesse.FitFilter}}}(See
CustomizingTestExecution.)
Any tables that are on the page are run through the Fit framework
(See http://fit.c2.com ).
So, for example, click the '''Test''' button (or type ''ALT-t'')
and see what happens to the table below:
|eg.Division|
|numerator|denominator|quotient()|
|10|2|5|
|12.6|3|4.2|
|-3|3|-1|
|100|0|0|
|33|3|11|
!path FitNesseRoot/files/examples/
TroubleshootingAcceptanceTests
----
You may also run FitNesseTests by using the CommandLineTestRunner!
Compare this text to the generated page and you will see that cells
in the table are separated by "|", that classpaths are indicated by
the keyword !path, and that links can be created by so-called
wiki words. You can get the details on these and other wiki syntax in
the UserGuide.
Save the edited page by pressing the Save button, and then re-run the tests using the "Test" link. This time the tests should run. The ones where the output is as expected are colored green. The ones where the output doesn't match the expected value are colored red and the expected and actual values are reported, as shown in Figure 2.

Figure 2. Running acceptance tests
This type of table corresponds to what is called the Column
Fixture. The columns headed numerator and
denominator correspond to instance variables whose values
are set to the values in the cell. The column headed
quotient() maps to a method call whose expected return
value is contained in the cell. The class containing these variables
and this method is the Division class in the package
eg.
Creating Your Harness
In our example, we will use the Action Fixture. With the Action
Fixture, you use the keyword start to load the class that will
contain the methods being called in the remainder of the table. To set
a value using the Action Fixture, you use the keyword enter
followed by a name-value pair. To initiate an action you use the
keyword press followed by the name of the action being
initiated. Finally, to examine a particular value, you use the
check keyword followed by the name of what you are checking and
its expected value.
Let's begin by creating a new page with a table that will lead the
student through the assignment. Edit the RunningAcceptanceTests page
and add the line FriendAssignment to the very end. Save
this page. Now you should see a "?" after the text you just added.

Click on the question mark, and the page FriendAssignment will be
created and you will be taken to the editing page. Here is a
simplistic version of a page that leads the students through this
assignment. The point is to demonstrate how easily you can use FIT and
FitNesse to specify what you want the student's application to do.
Inside of the directory !-FitNesseRoot/files/examples-!
create a new text file named '''Friend.java''' and save it inside
of the fitnesse directory.
|!- fit.ActionFixture -!|
| start | !-HelloWorldSetUp-! |
| check | friendDotJava | exists |
Next, add the text to Greeting.java that allows you to compile it
into the class file '''Friend.class''' and compile it.
|!- fit.ActionFixture -!|
| check | friendDotClass | exists |
Finally, add a method to Friend.java named getName() that returns
a String containing the name ''Gertrude''.
|!- fit.ActionFixture -!|
|check | name | Gertrude |
!path .
Adding the "!-" and "-!" around elements makes sure that the wiki
words won't be interpreted as links. Press Save and you should see
a page like Figure 3.

Figure 3. FriendAssignment in FitNesse
Notice that Test does not appear in the left
column. Click on Properties, check the
Test checkbox and then Save. Next, look at the three
tables. Each uses the ActionFixture class to process the tables. The
first loads the class HelloWorld. This class contains the
glue methods that call into the code your student is writing. The
method friendDotJava() is called, and it returns the
String "exists" if the file Friend.java exists. Otherwise, it returns
some error message that you can customize.
In the second table, you need to again specify that this table
uses the ActionFixture class, but you don't need to reintroduce
HelloWorld. The friendDotClass() method checks on the
existence of the file Friend.class. Finally, the third table calls the
method name(), which turns around and creates an instance
of Friend and calls the getName() method in Friend. It
checks the String that is returned to see if it is "Gertrude".
The fixture is a Java class that you write named
HelloWorld. Here is the code for HelloWorld.java, which
you save in the fitnesse directory.
import fit.Fixture;
import java.io.File;
public class HelloWorld extends Fixture {
private File source = new File("Friend.java");
private File classFile = new File("Friend.class");
public String friendDotJava(){
if (source.exists()) return "exists";
else return "Friend.java is not in the correct location.";
}
public String friendDotClass(){
if (classFile.exists()) return "exists";
else return "Friend.class has not been created yet.";
}
public String name() {
Friend friend = new Friend();
return friend.getName();
}
}
In order to compile HelloWorld, you'll need to create Friend.java,
which you can later delete. Add fit.jar to your classpath and compile
HelloWorld.java. Now you can delete Friend.java and Friend.class.
A student using your harness presses Test. They are
given feedback that indicates that Friend.java doesn't exist or is not in
the correct location. Once they meet that condition, they run
Test again and work on creating Friend.class by adding
public class Friend{} to Friend.java and compiling
it. Finally, once Friend.class exists, the middle table will pass and
the bottom table will be colored yellow with a warning that you have a
NoSuchMethodError, as Friend.getName() does
not exist. Once the student creates the getName() method
that returns the String "Gertrude", all of the tests will pass.
Summary
The intent of this article was to give you a feel for how you can lay out an assignment using FIT and FitNesse. Some assignments will require more than one wiki page for running tests, more than one fixture for supporting tests, or different types of fixtures. Students don't need to guess at what you want in an assignment. They can see the tests and they should have access to the corresponding Fixtures. This provides examples to them of working code that will be used to exercise their code.
- Login or register to post comments
- Printer-friendly version
- 2135 reads



