ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Teaching Java the Extreme Way
Pages: 1, 2, 3

Extreme Teaching

Now that you're remembering back to your early days, perhaps you used to type in -- I mean, perhaps you knew people who would type in -- long passages of a program and then save and try to compile it. They spent a lot of time debugging the program. Maybe they put print statements here or there and commented out sections that weren't working, rather than use an actual debugger. In any case, they spent a lot of time trying to track down what usually amounted to little typos.



In extreme programming, you take very small steps. The goal is for the code to always compile, and for all of the tests to pass. We might write a couple of lines of code and then compile and run all of the tests. If something goes wrong, we have a good idea where to look for our mistake. Here's a pseudo-code explanation of how we might proceed with the current program.

Running the Harness

First, before any tests are written, you should be able to run the test harness and get some indication that it is up and running. In this case, there will be an executable .jar file that you can run by double-clicking it. When it starts up, you will see this:


Testing For the Existence of the Friend Class

Go ahead and click the button. Until the student has successfully created the shell for a Friend class, they will see something like this:

Let's write a test to make sure we can create an object of type Friend. We'll do something like this:

public void testFriendExisits(){
   Friend friend = new Friend();
   checkExistenceOf(friend);
}

This test shouldn't compile, because we haven't created a Friend class yet. So we create a Friend class.

public class Friend(){

}

The code compiles and the test passes. Note that this exercise required that we do the following.

  1. Use a text editor to create the code for the class Friend.
  2. Save the code as Friend.java in a location in our classpath.
  3. Compile Friend.java using javac or another compiler.
  4. Run the test harness that checks the code.

This means that your two-line shell for the Friend class already serves as a system check, just like the classic version of HelloWorld, without bogging you down in a ton of details. You typed in Java code and you receive some sort of feedback that your work was successful.

Testing For the Signature of the getName() Method

Once a Friend class exists in the correct location, the student will see this feedback:

The next step is to write a test to make sure that the Friend class contains a method, getName(), that returns a String that is treated as the user's name. Our pseudo-code for the test might look like this:

public void testFriendReturnsName(){
   Friend friend = new Friend();
   checkThisMethodReturnsAString(friend.getName());
}

Now you can see the benefit of working step by step. Once the first test passes, we know that we can create an object of type Friend. Even though the test seems trivial, we always run it as part of our test suite. If, down the road, someone changes the source code and breaks this test, we'll immediately know what went wrong and we'll know what needs fixing. These tests protect us against future changes.

In this second test, we create a Friend object, invoke its getName() method and check that it returns a String. The instantiation is repeated code from the first step. When we look at JUnit, we'll use its facility for refactoring this common code to a single setUp() method. With this test method, the student's code can't compile if the signature of getName() isn't correct. This is a bit restrictive for a first assignment. In our actual harness code, you'll see that we test for a correctly-named method and a correct return type separately, and give helpful feedback in either case. The student can get the test to pass with code like this:

public class Friend { 
   public String getName(){
      return "Any old String you want. " +
         "It might be a name or it might be a meaningless sentence or two.";
   }
}

Testing the Actual Value Returned

At this point, everything is syntactically correct in the student code. You may have noticed so far that getting the code to compile led to the test being passed. We'll write one more test that is a bit far-fetched in this example, but will serve to make a point. Suppose that I know the student's name is Elena. I may want to test that the value that is returned by the getName() method matches "Elena." This test might look like this:

public void testFriendReturnsName(){
   Friend friend = new Friend();
   checkThisStringHasGivenValue(friend.getName(), "Elena");
}

This code immediately compiles, because the class and the method exist and the method returns a variable of the correct type. The test, however, will fail until the student changes the getName() method to this:

public String getName(){
   return "Elena";
}

Often, the purpose of unit tests is to test the return value of different methods in different situations. In the current example, this is a bit silly, and so our test harness won't do it. Once the signature of getName() is correct, our harness will display whatever the student has used as the value being returned. They will see a screen like this:


Summary

In our revised HelloWorld assignment, the students have had to read existing code and craft an appropriate response. From the start, the return type of a method has meaning to them, because they are required to use it. They have been required to create from scratch a class and a method that conform to someone else's specifications. From the start, they understand that they may be only writing part of an application and that they have to be able to understand, conform to, and later, possibly contribute to the public interface.

Pages: 1, 2, 3

Next Pagearrow