SE450: Lecture 4 (Template Method/Unit Testing/Ant)

Contents [0/41]

Overview of Today's Class [1/41]
Java: Constructors [2/41]
Java: Exceptions [3/41]
Java: Casting [4/41]
Java: Overloading [5/41]
Java: Overriding [6/41]
Java: Hiding [7/41]
Java: this and super [8/41]
Java: Types, Interfaces, Concrete and Abstract Classes [9/41]
Java: Extending Classes [10/41]
Classes and Inheritance [11/41]
Patterns: The Template Method Pattern [12/41]
Patterns: Simple Template Method Example [13/41]
Patterns: Another Template Method Example [14/41]
Patterns: Strategy vs. Template Method [15/41]
Ant: What is it? [16/41]
Ant: Download [17/41]
Ant: Setup [18/41]
Ant: Environment Settings [19/41]
Ant: Running Ant [20/41]
Ant: The development directory [21/41]
Ant: The build file - intro to XML [22/41]
Ant: A concrete example [23/41]
Testing: Testing Types [24/41]
Testing: Approaches [25/41]
Testing: Why write Unit Tests? [26/41]
JUnit: Introduction [27/41]
JUnit: Goals [28/41]
JUnit: Download [29/41]
JUnit: Installation [30/41]
JUnit: The JUnit Framework [31/41]
JUnit: Writing A Test [32/41]
JUnit: Creating a Fixture [33/41]
JUnit: Creating a Suite of Tests [34/41]
JUnit: Executing Tests [35/41]
JUnit: Running from the Command Line [36/41]
JUnit: Running Tests with the GUI [37/41]
JUnit: An example [38/41]
JUnit: Setting up your source tree [39/41]
JUnit: Integrating with Ant [40/41]
Homework [41/41]

Overview of Today's Class [1/41]

Extending classes, other Java/OO

Template/Hook/Abstract Methods

Classes and Inheritance

Template Method Pattern

An introduction to Ant

Testing/Unit Testing

JUnit

Homework Assignment

Java: Constructors [2/41]

Constructor is a special form of static method on an unformed object.

The constructor is always needed, and is always called on object creation

In java, the default no-arg constructor always exists, and is supplied by default by the language

If a constructor is supplied with arguments, a no-arg constructor will have to be supplied if needed, the default is no longer present. In general, write a no-arg constructor if you are using it.

an initialization block can be supplied to run code once. It is run before any constructor

Java: Exceptions [3/41]

Exception basics

Checked vs. Unchecked

Error, Exception, RuntimeException

throw

try, catch, finally

Checked exceptions are checked by the compiler so that your method should only throw exceptions declared to be thrown. A checked exception extends Exception.

Unchecked exceptions extend RuntimeException and Error, meaning the compiler will allow them to be thrown by any method.

Officially, you are supposed to only create checked exceptions, but there are arguments against this. You can throw an Error or a RuntimeException without adding a throws clause. There are a number of these already available in the java APIs.

Java: Casting [4/41]

        public class Test {
          public static void main(String args[]) {
            Base obj1 = new Sub1();  // compiles ok, runs ok (upcast)
            Base obj2 = new Sub2();  // compiles ok, runs ok (upcast)
            Sub1 obj3 = obj1;     // compiler error
            Sub1 obj4 = (Sub1) obj1; // compiles ok, runs ok (explicit downcast)

            Sub1 obj5 = (Sub1) obj2; // compiles ok, run-time exception (bad downcast)
            Sub2 obj6 = (Sub2) obj4; // compiler error (crosscast)
          }
        }

        class Base {} 
        class Sub1 extends Base {} 
        class Sub2 extends Base {} 
        

Java: Overloading [5/41]

Overloading:Overloading refers to the ability to allow different methods or constructors of a class to share the same name. The name is said to be overloaded with multiple implementations.

When can you overload?: Two methods or constructors in the same class can be overloaded, i.e., sharing the same name, if they have either different number of parameters or same number of parameters but of different types. In other words, no two methods or constructors in the same class may have identical signatures.

What does this program print? Why?

/*
 * CollectionClassifier.java
 *
 */

import java.util.*;

/**
 *
 * @author  From "Effective Java" by Joshua Bloch
 */
public class CollectionClassifier {
    
    /** Creates a new instance of CollectionClassifier */
    public CollectionClassifier() {
    }
    
    public static String classify(Set s) {
        return "Set";
    }
    
    public static String classify(List l) {
        return "List";
    }

    public static String classify(Collection c) {
        return "Unknown Collection";
    }
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Collection[] tests = new Collection[] {
                new HashSet(),
                new ArrayList(),
                new HashMap().values()
        };
        for(int i = 0; i < tests.length; i++) {
            System.out.println(classify(tests[i]));
        }
    }
    
}
         

Note that Java allows overloading across subclasses, something that C++ doesn't allow. This can cause all sorts of confusion. The best rule is don't do it

Java: Overriding [6/41]

static and dynamic: What do these words mean?

Dynamic versus static dispatch.

The word static refers to properties of the text of a program (some synonyms are lexical and compile-time).

The word dynamic refers to properties of an execution of a program (a synonym is run-time).

Dynamic binding proceeds as follows:

  1. Step 1. currentClass = the class of the object referenced by var.
  2. Step 2. if method m() is implemented in currentClass then the implementation of m() in currentClass is invoked. else currentClass = the superclass of currentClass, and repeat Step 2.

Overriding:refers to the introduction of an instance method in a subclass that has the same name, signature, and return type of a method in the superclass. Implementation of the method in the subclass replaces the implementation of the method in the superclass.

All method in Java are polymorphic (virtual in C++) unless they are declared final in which case they can't be overridden

Overridden methods are chosen at run time (dynamically), overloaded methods are chosen at compile time (statically).

Example:

        class B {
            public void m2()
            ...
        }
        class C extends B {
            public void m2()
            ...        
        }
        B b = new B();
        C c = new C();
        b.m2(); // invoke the m2( ) in class B
        c.m2(); // invoke the m2( ) in class C
        

Overriding a method with a different signature is not allowed:

        class B { 
            public void m3(int i) {
                //
            }
        }

        class C extends B { 
            public void m3(char c) {
                //
            }
        }
        

Java: Hiding [7/41]

Hiding refers to the introduction of a field (instance or class) or a class method in a subclass that has the same name as a field or a class method in the superclass.

        public class A {
            int x;
            void y() { ... }
            static void z() { ... }
        }
        public class B extends A {
            float x;                // hiding
            void y() { ... }        // overriding
            static int z() { ... }  // hiding
        }
        

You should avoid hiding

Java: this and super [8/41]

in Java, this is a reference to the instance that is the receiving instance of a method call.

this is usually used in two ways:

  1. to pass the object instance as a parameter to another method
  2. to access instance fields that are shadowed
        public someMethod(SomeClass o) {
            o.hereIAm(this);
        }
        

You will see the second one in many setter methods

        public void setSomeField(String somefield) {
            this.somefield = somefield;
        }
        

this is just used to define scope for the variable

The keyword super represents an object reference having the same value as this, but it behaves as if it were a reference to the superclass.

It is used many times to call constructors. (more on this later)

Java: Types, Interfaces, Concrete and Abstract Classes [9/41]

Java allows for single inheritance in class extension, but multiple inheritance in interface implementation

        class Foo extends Bar implements Baz, Boo {
            // implementation
        }
        

interfaces can be thought of as "pure virtual" abstract classes

Diamond inheritance is supported in Java with interfaces only

abstract classes are classes with abstract methods. They can't be instantiated, and the abstract methods must be implemented by subclasses, unless the subclass is itself abstract

The Type relationships in Java are:

For every class C (that is not Object), C is a subtype of Object. This has implications for every class that you write.

A value of a subtype can appear wherever a value of its supertype is expected. In other words, a value of a subtype can always substitute for a value of its supertype.

Java: Extending Classes [10/41]

All classes that are not declared final can be extended in Java

The implementation of all methods that are not declared final can be overridden

The initialization of an extended class has two phases:

  1. The initialization of fields inherited from the superclass
  2. The initialization of fields declared in the extended class

The field initialization is done by the constructor (or initialization block) of each class. The constructors are called up the chain. The default super() constructor is called by default. If it is not implemented in the superclass, a compile error will result. Different constructors may be called, ex. super(param1, param2), but the call to the superclasses constructor must the the first statement in the constructor

Classes and Inheritance [11/41]

To set up the discussion of the Template Method pattern, we need to review a little bit of the Java that we have learned already.

What happens when a method is called on a class that is inheriting functionality from an abstract class?

What happens when an abstract class calls abstract methods?

Remember, the type of a reference variable is static (determined at compile time), but the type of the object is dynamic (determined at run time). If the method being invoked is not overridden by the subclass, it will invoke the superclass's method. If that method invokes abstract methods, they are invoked on the subclass (which has to implement all abtract methods or declare itself abstract). This allows the abstract class to be the template method that calls the methods in the subclass. Or, the superclass can provide default (usually empty) "hook" methods that the subclass can override. These are then hooks into the algorithm if the subclass needs them.

Patterns: The Template Method Pattern [12/41]

Patterns: Simple Template Method Example [13/41]

A simple example.

BaseClass/AbstractClass

ConcreteClass


abstract class BaseClass {
    public BaseClass() {
	
    }
    abstract void primitiveOperation1();
    abstract void primitiveOperation2();

    final void templateMethod() {
	for(int i = 0; i < 5; i++) {
	    primitiveOperation1();
	    primitiveOperation2();
    }
  }
}


class ConcreteClass extends BaseClass {
  void primitiveOperation1() { 
    System.out.print("Hello ");
  }
  void primitiveOperation2() { 
    System.out.println("World!");
  }
}

public class TemplateMethod {
  BaseClass concrete = new ConcreteClass();
  public void test() {
      concrete.templateMethod();
  }
  public static void main(String args[]) {
    new TemplateMethod().test();
  }
}

          

We will also look at another example in JUnit code later tonight.

Patterns: Another Template Method Example [14/41]

GenericClass: Plotter

import java.awt.*;

public abstract class Plotter 
    extends java.applet.Applet {
  public abstract double func(double x); 
  public void init() {
    d = getSize(); 
    String att = getParameter("xratio");
    if (att != null) 
      xratio = Integer.parseInt(att); 
    att = getParameter("yratio");
    if (att != null) 
      yratio = Integer.parseInt(att); 
    att = getParameter("xorigin");
    if (att != null) 
      xorigin = Integer.parseInt(att); 
    else 
      xorigin = d.width / 2; 
    att = getParameter("yorigin");
    if (att != null) 
      yorigin = Integer.parseInt(att); 
    else 
      yorigin = d.height / 2; 
  }
  public void paint(Graphics g) {
    drawCoordinates(g); 
    plotFunction(g);
  }  
  
  /** the dimension of the viewing area */ 
  protected Dimension d; 
  
  /** The color used for plotting */ 
  protected Color color = Color.black; 
    
  /** The position of the origin of the coordinate system */ 
  protected int xorigin, yorigin; 
  
  /** The number of pixels between 0 and 1 in x and y direction */ 
  protected int xratio = 100, yratio = 100; 
  
  protected void plotFunction(Graphics g) {
    for (int px = 0; px < d.width; px++) {
      try {
        double x = (double)(px - xorigin) / (double)xratio; 
        double y = func(x); 
        int py = yorigin - (int) (y * yratio); 
        g.fillOval(px - 1, py - 1, 3, 3); 
      } catch (Exception e) {} 
    }
  }
  protected void drawCoordinates(Graphics g) {
    g.setColor(Color.white); 
    g.fillRect(0, 0, d.width, d.height); 
    
    g.setColor(color); 
    g.drawLine(0, yorigin, d.width, yorigin); 
    g.drawLine(xorigin, 0, xorigin, d.height); 
       
    g.setFont(new Font("TimeRoman", Font.PLAIN, 10)); 
    int px, py; 
    int i = 1; 
    py = yorigin + 12; 
    g.drawString("0", xorigin + 2, py);  
    for (px = xorigin + xratio; px < d.width; px += xratio) {
      g.drawString(Integer.toString(i++), px - 2, py);  
      g.drawLine(px, yorigin - 2, px, yorigin + 2);  
    }
     
    i = -1; 
    for (px = xorigin - xratio; px >= 0; px -= xratio) {
      g.drawString(Integer.toString(i--), px - 2, py);
      g.drawLine(px, yorigin - 2, px, yorigin + 2);    
    }
     
    i = 1;
    px = xorigin + 4; 
    for (py = yorigin - yratio; py >= 0; py -= yratio) {
      g.drawString(Integer.toString(i++), px, py + 4);  
      g.drawLine(xorigin - 2, py, xorigin + 2, py);  
    }
     
    i = -1; 
    for (py = yorigin + yratio; py < d.height; py += yratio) {
      g.drawString(Integer.toString(i--), px, py + 4);
      g.drawLine(xorigin - 2, py, xorigin + 2, py);    
    }
     
  }
  
}

        

ConcreteClass: PlotSine, PlotCosine


public class PlotCosine extends Plotter {
  public double func(double x) {
    return Math.cos(x); 
  }
}
public class PlotSine extends Plotter {
  public double func(double x) {
    return Math.sin(x); 
  }
}

        

Patterns: Strategy vs. Template Method [15/41]

Strategy is a good example of Subtyping

Template Method is a good example of Subclassing

Strategy helps you define a family of similar algorithms

Template Method helps you define a skeleton of an algorithm

Strategy allows you to code to a common inteface and change implementations easily by plugging in new algorithms

Template Method allows you to code portions of an algorithm once, allowing subclasses to modify or enhance the behavior of the algorithm

Ant: What is it? [16/41]

Ant

Ant stands for Another Neat Tool

Basically, Ant is a Java based build tool. It uses Java to build Java code for you. It also happens to have a number of interesting features that make life for the Java developer a lot easier.

Ant has been described as make without make's wrinkles.

So what is make?

Ant: Download [17/41]

Download it from here. You should get the .zip version if you are on Windows, get the tar.gz if you are on Unix. For now, just get jakarta-ant-1.5.1-bin[.zip or .tar.gz].

On Windows, unzip it to a directory using your favorite zip tool. With WinZip, you just double click on the file, click the extract button, and choose a good directory to unzip it to. I recommend something close to the root of your drive, like D:\ or C:\ or C:\se450. Once extracted, it will be in a directory called jakarta-ant-1.5.1. We will call this directory ANT_HOME

On Unix, extract the ant tarball to a directory where you have permissions to keep applications. On linux, this might be /usr/local, on HP it might be /apps or /opt. If you don't have root access, you can install Ant anywhere.

With Gnu tar, you can use the command tar -xvzf jakarta-ant-1.5.1-bin.tar.gz. With the standard tar, use something like gzip -c jakarta-ant-1.5.1-bin.tar.gz | tar xf - from the directory where you want the ANT_HOME to be.

With either download, you can check the PGP signature of the file using pgp or gpg.

Ant: Setup [18/41]

Ant is a command line tool, although it has been integrated with numerous IDEs. Since we are using the command line, it will fit in well with our development environment.

Once Ant has been extracted, you will need to setup your environment to run from the command line properly. This will involve setting the PATH and ANT_HOME environment variables.

From the command line:

On Windows:

        set ANT_HOME=C:\jakarta-ant-1.5.1
        set PATH=%PATH%;%ANT_HOME%\bin
        
On Unix (bash):
        export ANT_HOME=/usr/local/jakarta-ant-1.5.1
        export PATH=$PATH:$ANT_HOME/bin
        

Ant: Environment Settings [19/41]

To make this the default on your machine:

On Windows (NT-ish systems):

  1. Go to Start->Settings->Control Panel->System
  2. Click on Environment
  3. In the Variable text box, add ANT_HOME
  4. In the Value text box, add the path to your Ant install (i.e. C:\jakarta-ant-1.5.1)
  5. Click on the PATH environment variable.
  6. Edit it to be <previous settings>;%ANT_HOME%\bin
  7. Click Apply/OK

On Windows (2000/NT/XP-ish systems)

  1. Go to Start->Settings->Control Panel->System
  2. Click on Advanced->Environment Variables
  3. In the User variables section, click Add and add ANT_HOME in the Variable Name box.
  4. For the Variable value text box, add the path to your Ant install (i.e. C:\jakarta-ant-1.5.1)
  5. Click on the PATH environment variable (in either system or user variables) and click Edit.
  6. Edit it to be <previous settings>;%ANT_HOME%\bin
  7. Click Apply/OK

On Windows 9x:

Add an entry in your autoexec.bat for ANT_HOME and PATH that is identical to the settings above from the command line.

On Unix:

Add an entry in your .profile for ANT_HOME and PATH that is identical to the settings above from the command line.

Ant: Running Ant [20/41]

OK, enough setup. Now to run Ant. You should be able to go to a random directory in a command window and type ant and see something like the following on your screen:

Buildfile: build.xml does not exist!
Build failed

This is a good thing. This means ant is now working. Now we will look at the setup of your development directory and the build file to compile your code.

Ant: The development directory [21/41]

So far, you have been developing code in a directory, running java and javac by hand. Perhaps you have written .bat or .sh scripts to run these commands repetitively. You also have probably set your system classpath at the global level for this class.

By setting up Ant properly, we'll eliminate the need for this.

Create the following directory structure on your computer where you will work on your homework assignments

base directory (like c:/se450 or /home/username/se450
|
+------>build.xml
        /src
	/lib

The /src directory will contain all of your source files in their full package directories

The /lib directory will contain any third party .jar files that are supplied by me or others

Other directories will be created as we go through the quarter and by the build process itself.

Ant: The build file - intro to XML [22/41]

The build file is written in XML. This means that it has to conform to XML rules, as well as be properly configured for what Ant expects.

Some XML basics.

The Ant build file structure.

project
  property
  property
  target
    task
    task
  target
    task
    task

Properties are settings that you use throughout your build file. They are set, and then dereferenced in the build file by using the following syntax: ${property}

A build file consists of multiple targets. Targets can be dependent on one another so they are always run in the correct order. For example, you would always need to run an init target that makes directories where your javadoc will go.

A target consists of multiple tasks. Ant contains built-in tasks for some of the following:

It also has optional tasks for things such as:

Ant: A concrete example [23/41]

An example build file (that built the solution to Homework 1!)

<?xml version="1.0"?>

<project name="se450" basedir="." default="compile">

    <!-- the classpath used for various tasks in the build process -->
    <path id="project.classpath">
      <fileset dir="lib">
        <include name="**/*.jar"/>
      </fileset>
      <pathelement location="." />
      <pathelement location="build" />
    </path>
        
    <!-- ================================================== -->
    <!--  Target: init                                      -->
    <!--  Sets properties, creates needed directories       -->
    <!-- ================================================== -->
    <target name="init">
        <!-- put name=value pairs in this file to override -->
	<!-- the properties below                          -->
        <property file="ant.properties" />
        <property name="apidoc" value="docs" />
        <property name="src" value="src" />
	<property name="build" value="build" />
	<property name="submission" value="submission" />
	<property name="test" value="test" />
	<property name="testresults" value="testresults" />
	<property name="instrument" value="instr" />
	<property name="repository" value="rep" />
	<property name="instrbuild" value="instrbld" />

	<!-- used in making submission file -->
	<property name="homework" value="hw1" />
	<mkdir dir="${build}" />
	<mkdir dir="${submission}" />
	<mkdir dir="${testresults}" />

    </target>

    <!-- ================================================== -->
    <!--  Target: compile                                   -->
    <!--  Compiles all the source code                      -->
    <!-- ================================================== -->
    <target name="compile" depends="init">
        <!-- Both srcdir and destdir should be package roots. -->
        <javac srcdir="${src}" 
	    destdir="${build}" 
	    debug="true" 
	    deprecation="true">
            <classpath>
                <path refid="project.classpath" />
            </classpath>

            <!-- To exclude some files: -->
            <!--
            <exclude name="com/foo/SomeFile.java"/>
            <exclude name="com/foo/somepackage/"/>
            -->
        </javac>
    </target>

    <!-- ================================================== -->
    <!--  Target: zip                                       -->
    <!--  Makes a submission zip file for the grader        -->
    <!-- ================================================== -->
    <target name="zip" depends="init,compile">
         <copy todir="${submission}" >
	   <fileset dir="${src}">
	     <include name="**/${homework}/*.java" />
	   </fileset>
	   <fileset dir="${test}">
	     <include name="**/${homework}/*.java" />
	   </fileset>
	   <fileset dir=".">
	     <include name="grader.txt" />
	   </fileset>
	 </copy>
         <zip zipfile="${submission}/submission.zip" 
	    compress="true" 
	    basedir="${submission}">
	    <include name="**/*.java" />
	    <include name="grader.txt" />
            <exclude name="**/*.class"/>
	    <exclude name="**/*.jar" />
        </zip>
    </target>

    <!-- ================================================== -->
    <!--  Target: compiletests                              -->
    <!--  Compiles all the test source                      -->
    <!-- ================================================== -->
    <target name="compiletests" depends="init">
        <!-- Both srcdir and destdir should be package roots. -->
        <javac srcdir="${test}" 
	    destdir="${build}" 
	    debug="true" 
	    deprecation="true">
            <classpath>
                <path refid="project.classpath" />
            </classpath>

            <include name="se450/**/*Test.java"/>

        </javac>
    </target>
    
    <!-- ================================================== -->
    <!--  Target: test                                      -->
    <!--  Runs a unit test.  Add or remove test lines       -->
    <!--  to run more tests.                                -->
    <!-- ================================================== -->
    <target name="test" 
            depends="compiletests,compile" 
	    description="Run Unit Test" >
        <junit printsummary="withOutAndErr" fork="yes">
	  <classpath>
            <path refid="project.classpath" />
          </classpath>
	  <formatter type="plain" />
	  <test name="se450.student.hw4.CourseTest"
	        todir="${testresults}" />
	</junit>
    </target>

    <!-- ================================================== -->
    <!--  Target: alltests                                  -->
    <!--  Runs all the unit tests.                          -->
    <!-- ================================================== -->
    <target name="alltests" 
            depends="compiletests,compile" 
	    description="Run All Unit Tests" >
        <junit printsummary="withOutAndErr" 
	       fork="yes" 
	       haltonfailure="yes">
	  <classpath>
            <path refid="project.classpath" />
          </classpath>
	  <formatter type="plain" />
	  <batchtest fork="yes" todir="${testresults}">
	    <fileset dir="${test}">
	      <include name="**/*Test*.java" />
	      <exclude name="**/AllTests.java" />
	    </fileset>
	  </batchtest>
	</junit>
    </target>

    <!--  ================================================== -->
    <!--  Target: all                                        -->
    <!--  Compiles and builds the submission file            -->
    <!--  ================================================== -->
    <target name="all" depends="init,zip" description="Build everything.">
        <echo message="Homework built. Submission zip is in ${submission}" />
    </target>

    <!-- ================================================== -->
    <!--  Target: javadoc                                   -->
    <!--  Makes javadoc of the source code                  -->
    <!-- ================================================== -->
    <target name="javadoc" depends="init" description="Javadoc for my API.">
        <mkdir dir="${apidoc}"/>
        <javadoc packagenames="se450.*" 
	    destdir="${apidoc}" >
            <sourcepath>
                <pathelement location="${src}"/>
            </sourcepath>
        </javadoc>
    </target>

    <!-- ================================================== -->
    <!--  Target: clean                                     -->
    <!--  Cleans up the build artifacts                     -->
    <!-- ================================================== -->
    <target name="clean" 
            depends="init" 
            description="Clean up class, test result files">
        <delete dir="${build}" />
	<delete dir="${testresult}" />
	<delete dir="${submission}" />
    </target>

</project>
    
        

Testing: Testing Types [24/41]

Types of Testing.

There are several different types of testing considered when developing applications.

The Unit Test is based on testing the software unit in isolation and is usually done by the developer of the software.

Functional Testing is based on the end-to-end testing of the application. This testing is done after integration of code from multiple developers. This test should be run on code that has already passed unit tests. This testing is many times run by the developers after integrating their code.

System Testing is usually done on in a production-like environment. Many times this is done by a separate Quality Assurance group.

Stress Testing is usually done to fine tune performance and test for scalability and robustness of an application. This is not always done for each release of the software, and is usually much more difficult to setup and perform.

Acceptance Testing is validation of the system from an end user perspective. They are exercising the system to verify that it meets their requirements as a user of the sytem.

Testing: Approaches [25/41]

Testing Approaches:


White Box - derives test cases based on the implementation of the code.

Black Box - derives test cases based on the specifications of the component alone, not considering implementation.

White Box coverage

As you can see, this is a lot easier to do at a lower (component) level than a higher (program) level.

Testing: Why write Unit Tests? [26/41]

What are some of the benefits of writing Unit Tests?

What does testing prove about our code?

At what level should you unit test your code?

Note that testing cannot prove that there are no bugs in code, it can just provide proof that the given scenarios tested do not result in a bug.

However, no testing at all doesn't even allow us to say that the code will work in any scenario (unless we can prove it mathematically, which is much harder).

So, theoretically, a developer hasn't completed their work until they can show through tests that their code works for the scenarios that the code was written for. This is very difficult to do when the code is integrated with other code, but is easier to do at a unit level.

Unit testing is usually performed at a public Class level. The standard way of doing this is in the main method. We will look at more efficient ways to do this.

JUnit: Introduction [27/41]

What is JUnit?

JUnit is an open source Unit Testing framework. It allows you to easily create tests for your Java code, then to run it quickly and easily, and format the results in a consistent fashion.

It springs from the xUnit test tools that are available in many languages at http://www.xprogramming.com/software.htm . The idea of xUnit unit testing frameworks started with a paper written by Kent Beck. He advocated that developers should write a Unit Test for each class that they write. His work started in Smalltalk, and JUnit is the Java extension of this work.

JUnit has become the standard testing framework for Java Development in most developer circles. It has been extended to allow testing of Web Applications (HTTPUnit), and Swing Applications (JFCUnit), along with multiple application specific tools and platforms.

JUnit integrates with Ant, allowing you to create an incremental development environment that can make your development easier and more efficient.

JUnit: Goals [28/41]

The goal of JUnit is simple: to provide a framework for testing code that developers will actually use. To do this, it needs to:

Unit tests should also:

JUnit: Download [29/41]

Download JUnit from www.junit.org. This will download a .zip file that contains the JUnit software, documentation, samples, and javadoc.

Make sure you get version 3.8.1 (the most recent, it will make your life much easier)

JUnit: Installation [30/41]

Installation of JUnit is very similar to Ant, but is simpler. Just unzip the .zip file onto your hard drive in any location. I recommend something like c:\, which will create a directory on your c: drive called junit3.7. JUnit is now installed.

You do not need to use Ant in order to use JUnit, but they still work together. I would recommend getting JUnit working first, and then add in Ant if you have time, since JUnit is tied directly to some homework points. We will (optionally) be integrating JUnit with Ant, so there are a few more steps to go through for that.

First, copy the file c:\junit3.8.1\junit.jar to your lib directory in your se450 development directory. Then, copy it to your ANT_HOME\lib directory as well. THIS STEP IS IMPORTANT IF YOU WANT TO USE JUNIT AND ANT TOGETHER!.

SET CLASSPATH=%CLASSPATH%;%ANT_HOME%\lib\junit.jar

The reason for this will be discussed later.

JUnit: The JUnit Framework [31/41]

JUnit is itself a good example of OO design. It contains a number of patterns that we will look at later. First, we will look at the various classes and interfaces that you will need to work with as you use the framework.

abstract classTestCase implements Test

The TestCase is the foundation of the framework. It is an abstract class that you must extend for each one of your TestCases.

The Test interface is used to collect TestResults

The concept of a Fixture is used to hold multiple tests that operate on the same set of objects. You can add more tests to your Fixture without much effort, allowing you to create tests to handle different scenarios.

Once you have multiple tests in a Fixture, you can run them together by creating a TestSuite. Since TestSuite also implements the Test interface, you can add TestSuites and Tests together and run them in one suite.

A higher level class diagram of JUnit.

JUnit: Writing A Test [32/41]

How do you write a test?

  1. Create an instance of TestCase
  2. Import junit.framework.* and any other classes you need to write your tests
  3. Create a constructor that has a single String argument and passes the argument to the superclass (TestCase). This is optional with JUnit 3.8 and above.
  4. Create a test method for the test you are writing. Make it public and start with the word test (all lower case).
  5. When you want to check a value, call assertTrue and pass in a boolean that is true if the test is successful. You can also use a form of one of the assertEquals, assertFalse, assertNotNull, assertNull assertSame, or assertNotSame from the Assert class.

(Part of) an example that tests the java.util.Vector class.

import junit.framework.*;
import java.util.Vector;

/**
 * A sample test case, testing <code>java.util.Vector</code>.
 *
 */
public class VectorTest extends TestCase {

	public VectorTest(String name) {
	        super(name);
	}

	public void testCapacity() {
	        Vector fEmpty;
	        Vector fFull;

		int size= fFull.size(); 
		for (int i= 0; i < 100; i++)
			fFull.addElement(new Integer(i));
		assertTrue(fFull.size() == 100+size);
	}

JUnit: Creating a Fixture [33/41]

A Fixture is just a special TestCase that sets up Objects for the tests to use (and share) for each test. This is done by just overriding a few methods that are part of TestCase. (what do you suppose their visibility is?)

To create a Fixture, add your instance variables and override setUp and (if needed) tearDown. These methods are called after each test is executed. setUp is used to initialize your variables, tearDown is used to free up resources at the end of the test.

(More of) an example that tests the java.util.Vector class.

package junit.samples;

import junit.framework.*;
import java.util.Vector;
import junit.extensions.*;

/**
 * A sample test case, testing <code>java.util.Vector</code>.
 *
 */
public class VectorTest extends TestCase {
	protected Vector fEmpty;
	protected Vector fFull;

	protected void setUp() {
		fEmpty = new Vector();
		fFull = new Vector();
		fFull.addElement(new Integer(1));
		fFull.addElement(new Integer(2));
		fFull.addElement(new Integer(3));
	}

	public void testCapacity() {
		int size= fFull.size(); 
		for (int i= 0; i < 100; i++)
			fFull.addElement(new Integer(i));
		assertTrue(fFull.size() == 100+size);
	}

	public void testContains() {
		assertTrue(fFull.contains(new Integer(1)));  
		assertTrue(!fEmpty.contains(new Integer(1)));
	}
	public void testElementAt() {
		Integer i= (Integer)fFull.elementAt(0);
		assertTrue(i.intValue() == 1);

		try { 
			Integer j= (Integer)fFull.elementAt(fFull.size());
		} catch (ArrayIndexOutOfBoundsException e) {
			return;
		}
		fail("Should raise an ArrayIndexOutOfBoundsException");
	}

}

JUnit: Creating a Suite of Tests [34/41]

Tests should be run together, and it should be easy to do this. The way that JUnit does this is by providing the TestSuite class. The TestSuite is a Composite pattern. It allows tests to be grouped and run together.

There are two ways to create a TestSuite, you can create it dynamically or statically. When you do it dynamically, you can create the test using the single argument constructor, or you can allow JUnit to generate your suite automatically (using Java Reflection).

Or to do it the type-safe way (so errors are caught at compile time)...

        public static Test suite() {
	        TestSuite suite= new TestSuite();
	        suite.addTest(
                    new VectorTest("clone") {
                         protected void runTest() { testClone(); }
                    }
		);
		return suite;
	}

How do you think the test gets run? Hint, it is via a pattern we have already studied.

To do it dynamically ...

	public static Test suite() {
	        TestSuite suite = new TestSuite();
		suite.addTest(new VectorTest("testClone"));
		suite.addTest(new VectorTest("testCapacity"));
		return suite;
	}
        

To do it automatically ...

	public static Test suite() {
		return new TestSuite(VectorTest.class);
	}

In the automatic way, JUnit uses Java Reflection to take each method that starts with "test" and takes no arguments, and add it to the suite. If you use this method, make sure you follow the naming convention for tests. You should follow the convention either way, but ignoring it prevents you from using this time saver.

Note: you will most often want to use one of the latter two methods. They are more readable and easier to work with, and most examples will use that method.

The type safe way of creating an anonymous inner class (more on these another lecture) contains the runTest method is using the Template Method pattern. The runTest method is the Template Method. It is called via the run() method of the TestCase class.

Actually, JUnit will automatically extract your test methods if you don't define a suite method, but it is best to define one so you can have better control over your tests, as well as to make them more readable.

JUnit: Executing Tests [35/41]

Great, so now you have all kinds of tests. How do you run them?

The first way would be to run it directly from the test itself via a main() method. You would instantiate a TestCase, then call the run method on it to execute the test.

            Test test= new MathTest("testAdd");
	    test.run();

This is a simple way, but not very extensible or easy to automate.

Second, you can use a TestRunner to run your tests. You have the choice of using a textual or graphical TestRunner. When integrating with Ant, we will use the textual one, but you will probably want to use the graphical one at one point or another to debug your code.

JUnit: Running from the Command Line [36/41]

To run the command line version of JUnit (the one run from inside ant)

java junit.textui.TestRunner
And you will get the following usage statement:
Usage: TestRunner [-wait] testCaseName, where name is the name of the
TestCase class

Just supply the full name of the test you want to run to the application, and it will run the test. You will see a . for each test run, and an F for each failed test.

The -wait parameter is useful for those of you who might run a test in a command window in Windows 9X from a batch file, and the window disappears. The test will stop and wait for a carriage return.

Sample output:

...
Time: 0.01

OK (3 tests)

In this case, there were 3 tests and all passed. If there were failures, we would see something like this:

...F
Time: 0.01
There was 1 failure:
1) testHardGrade(se450.student.hw3.MainTest)junit.framework.AssertionFailedError: Hard Grade expected:<D>
 but was:<A>
        at se450.student.hw3.MainTest.testHardGrade(MainTest.java:97)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)

FAILURES!!!
Tests run: 3,  Failures: 1,  Errors: 0

As you see it is a bit more dramatic.

The junit.jar file, your compiled test code, and your compiled code that is being tested must all be in your classpath for the TestRunner to succeed. In your se450 development directory, your CLASSPATH would need to be set to build;lib\junit.jar to run correctly.

JUnit: Running Tests with the GUI [37/41]

To run the tests with the JUNit GUI, you have two options. You can run the AWT GUI, or the Swing GUI.

For AWT, run

java junit.awtui.TestRunner
For Swing, run
java junit.swingui.TestRunner

Just like above, you need to have junit.jar and your compiled tests and code in your classpath in order to run the tests. The swing version of the JUnit GUI will automatically search your classpath for test cases, generating a list so you can pick the tests to run from the GUI. If you see red, your test failed. Green, it passed.

JUnit: An example [38/41]

Here is an example set of tests that exercise the getGrade methods of the three strategies from the last homework assignment. These tests do not offer a guarantee that the implementation is correct for every case, but you can see that it covers the differences in the different classes.

package se450.mwright1.hw2;

import junit.framework.*;

/**
 * A sample test case, testing <code>se450.mwright1.hw2.Student</code>.
 *
 */
public class StudentTest extends TestCase {
    
    protected Student student;

    protected void setUp() {
	student = new Student("Test", "Student");
	student.setProject1(50);
	student.setProject2(75);
	student.setProject3(100);
	student.setMidterm(66);
	student.setFinalExam(90);
    }
    
    public StudentTest(String name) {
	super(name);
    }
    
    public static Test suite() {
	return new TestSuite(StudentTest.class);
    }
    
    public void testConstructor() {
	assertTrue(student.getFirst().equals("Test"));
	assertTrue(student.getLast().equals("Student"));
    }
    
    public void testGrade() {
	assertTrue(student.getTotal() == 78);  
	assertTrue(student.getGrade().equals("C"));
    }

    public void testToString() {
	assertTrue(student.toString().equals("[Student: Test Student ]"));

    }
    
}

When writing Unit tests, ask yourself these questions:

Write these types of tests. Then, whenever you find a bug, add a test for that bug. Write the test that exposes your bug first. Then, modify your code to make it pass. Then, never get rid of the test and the bug will never reappear.

JUnit: Setting up your source tree [39/41]

Problem
We need to have test code that can fully test our code but don't want to expose all of it via public interfaces (this breaks encapsulation, after all!), but we also don't want to deploy our test cases into production with all the rest of our code.

Solution We can do this by keeping our source code in a separate directory and using standard naming conventions. Then we can compile our test classes and do unit testing, then delete them and rebuild without them at any time. By putting code in the same package, we can expose the implementation of our code to the test code easily.

You should follow the following coding conventions when writing tests for this class:

JUnit: Integrating with Ant [40/41]

.jar files - put junit.jar in both your own lib directory, and %ANT_HOME%\lib.

build.xml changes - pay attention to the compiletests, test, and alltests tasks.

A test result file looks like this:

TEST-se450.mwright1.junit.VectorTest.txt


Testsuite: se450.mwright1.junit.VectorTest
Tests run: 3, Failures: 0, Errors: 0, Time elapsed: 0.02 sec

Testcase: testCapacity took 0.01 sec
Testcase: testContains took 0 sec
Testcase: testElementAt took 0 sec

compiling tests - run the compiletests task in Ant, or use javac like before but with the junit.jar file in your classpath.

run tests using either:

  1. junit.textui.TestRunner
  2. junit.awtui.TestRunner
  3. junit.swingui.TestRunner
  4. ant test - set the test name in ant.properties
  5. ant alltests

Both of the ant tasks use a version of junit.textui.TestRunner

Homework [41/41]

Goal: To extend your Flight class to provide more functionality, to get familiar with the canonical form of classes, and to get familiar with JUnit - you will write the JUnit unit tests to prove that your code works.

Read Chapter 3 of Joshua Bloch's Effective Java. It will help you as you do the homework assignment this week. You can also refer to Jia Chapter 6.

Take your existing Flight class from hw3. Put it in the package se450.<studentid>.hw4.

Implement the java.lang.Comparable interface in your Flight class. Override the compareTo method in this interface so that Flightss are sorted properly. Proper sorting should be such that the following is true:

Also write an equals method for Flight. A Flight is equal to another if they have the same Airline and Flight number

Write a test using JUnit which proves that your Comparable implementation and equals function actually works. You should create at least three (maybe more) Flight objects in your setUp method. They should be stored in an array (or Collection) of Flight objects. Have your test sort the code using one of the Arrays.sort or Collections.sort methods.

To prove that your equals methods work, make sure you meet the contract, i.e. you support the reflexive, symmetric, transitive, consistent, and null reference properties of the equals method. Your unit test for equals should exercise all of these properties. See chapter 3 of Block, the JavaDoc for Object.equals(), and 6.3.2 of Jia for help.


Due next Monday at 5:30 PM.


Revised: 2003/1/26