Contents [0/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:
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:
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:
Object
Object
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:
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%\binOn 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):
->
Settings->
Control
Panel->
System
ANT_HOME
On Windows (2000/NT/XP-ish systems)
->
Settings->
Control
Panel->
System
->
Environment Variables
ANT_HOME
in the Variable Name box.
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
true
and false
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 |
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?
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
Actually, JUnit will automatically extract your test methods
if you don't define a |
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.TestRunnerAnd 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.TestRunnerFor 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.
compiletests
task will first compile your own
code, then compile the tests for your code. If you aren't using
ant, you need to make sure you do both of these steps manually.test
task will run one test and print a plain summary to the
testresults folderalltests
task will find all classes with the
name pattern of *Test and run them all at once. It will print
the results to a testresult file - one per Test class.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:
junit.textui.TestRunner
junit.awtui.TestRunner
junit.swingui.TestRunner
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 Flights
s 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