Extreme Teaching: Introducing Objects Blog

Version 2


    Often, the people who spend their days doingobject-oriented programming don't communicate with those wh o 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 runsHelloWorld. After all of this typing, the student is rewarded by seeing "Hello, world." in the console window.

    I've written elsewhereabout 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 themain() 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 argsvariable, 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 intoFriend.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 Friendwith 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 FITframework 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 fitnessedirectory contains two run files: run.bat for Windows andrun.sh for Unix. You can also run usingfitnesse.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:


    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 tohttp://localhost:8099/FrontPage and be greeted by "Welcome to FitNesse". Follow the link toFitNesse.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 isfitnesse20030728.zip, you will need to take the following steps to clean up your FitNesse installation. Go to the URLhttp://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 tohttp://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 findeg 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 anddenominator correspond to instance variables whose values are set to the values in the cell. The column headedquotient() 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 keywordenter 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 theRunningAcceptanceTests page and add the lineFriendAssignment 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 pageFriendAssignment 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 theTest 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 fileFriend.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. ThefriendDotClass() 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 thegetName() 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 namedHelloWorld. Here is the code forHelloWorld.java, which you save in the fitnessedirectory.

    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. Addfit.jar to your classpath and compileHelloWorld.java. Now you can delete Friend.javaand 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 creatingFriend.class by adding public class Friend{}to Friend.java and compiling it. Finally, onceFriend.class exists, the middle table will pass and the bottom table will be colored yellow with a warning that you have aNoSuchMethodError, as Friend.getName()does not exist. Once the student creates the getName()method that returns the String "Gertrude", all of the tests will pass.


    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.