CS 235 - Week 14 Lecture - 2015-11-30
**********
A TASTE of UNIT TESTING with JUnit
**********
* unit testing - writing tests for each "unit" of
code
* in Java, being very object-oriented,
you'd write unit tests for each method in
a class;
(but, most testing frameworks will allow
you to write a testing class for each class,
writing test methods to test the methods
of that "class under test", the class being
tested)
* IDEALLY -- you write the unit tests for a method
before you write the method
* TDD - test-driven development - takes this even
further, says to write 1 test, write code to make
that pass, write the next test, write code to
make that pass, etc.!
* there are a number of unit testing frameworks
out there -- a number that are Java-specific;
one of the "classics" is called JUnit, and that's
what we are introing here;
* junit.org has MUCH info on JUnit,
including its API (look under the JavaDocs link
under "Welcome"!)
(it is not part of the Java SDK...)
BUT it is included in many IDEs,
including Eclipse, NetBeans, and even DrJava
* (I hope the following is up-to-date...)
* to write a JUnit testing class,
import the following:
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
* common naming standard -- the unit tests for
a class Foo would be in a class named FooTest
SO - if I wanted to write unit tests for my
GameDie class from the Week 1 lab,
I'd make a class GameDieTest
* typically, you'll declare some instances of
the class being tested as private data fields
* THEN, write a method named setUp that
instantiates these private data fields
AND does any other setup I want to be
done prior to EACH test method
how does this happen?
with the help of the above imports
AND additional ANNOTATIONS:
@Before
...this annotation, usually put directly before
this setUp method,
means, run this BEFORE each test method
@Before
public void setUp() throws Exception
{
... desired set up statements ...
}
* for each test method,
* precede it with a @Test annotation,
* and (typically) name it starting with the
word test followed by (mostly) the name of the method
being tested (uppercase its first letter for
Java's traditional camelCase):
to test getNumRolls, you'd write testGetNumRolls
to test getNumSides, you'd write testGetNumSides
* then, put a combination of statements and
assert calls to test that method;
* the Assert class has NUMEROUS static assert methods;
here are two:
assertEquals - optionally first expects a string
describing what should happen with this
test,
then the expected value,
then the call being tested
(for double values, add an additional
argument -- the delta, how "close" is
close enough)
assertTrue - optionally first expects a string
describing what should happen with this
test,
then a boolean expression (typically
including a call to the method being
tested) that should be true if all
is well
some others:
assertFalse, assertSame, assertArrayEquals,
assertNotSame, assertNull, assertNotEquals,
fail
* junit.org has API, including package org.junit, which
contains Assert class, and javadoc for these
static assert methods;
...remember, follow the JavaDocs link under
"Welcome"
* CAN run from command line, BUT since this package
is not part of standard Java SDK, there are additional
steps to do so -- see junit.org
* BUT many Java IDEs include support for JUnit, built-in --
for example, in DrJava, if you have a class and its
JUnit test class open,
then you can click the "Test" button (upper right) to
run the JUnit tests, and get green, red indicators for
methods whose assertions passed (green) and failed (red)
* (after class) - oh! also can run using
Tools->Test All Documents
Tools->Test Current Document
* (and other IDEs -- Eclipse, pretty sure NetBeans -- have even
fancier support;
...some IDEs can be set up to run unit tests
every time you compile!
...and some source code version systems will RUN unit tests
on code being added, and won't allow the code to be added
if they don't pass;)
**********
a tiny bit more about just TWO of the (many)
options for file input/output
**********
* reading from, writing to a file!
* TINY bit about this on pp. 84-85, in Chapter 3,
of "Core Java"!
**********
the File class
**********
* LITTLE ASIDE:
* this is from Volume II of "Core Java"...
* package java.io has MANY input/output related
classes, mostly stream-based but even a few
record-based;
* also includes a useful class File, which
[from Core Java Volume II] "encapsulates
the functionality that you will need in order to
work with the file system on the user's machine"
* convenient: one of File's constructors expects a
String representing the name of a file,
and creates a File instance representing that
file;
* File includes methods that let you...
* delete a file
* create a directory
* list directory contents
* query the size of a file
* query when a file was last modified
* query if a file is readable or writable
* and more...
* NOTE: except for nuking them outright,
you cannot modify the CONTENTS of a file
using File methods --
you need an input or output stream for
that (via Scanner or PrintWriter or one
of MANY other classes in java.io)
**********
(back to considering a bit more on file input/output)
**********
* since Java 5, we have seen that we can read from interactive input
using Scanner
* (recall:
import java.util.*;
...
Scanner in = new Scanner(System.in);
System.out.println("please enter a line: ");
String enteredLine = in.nextLine();
System.out.println("please enter an integer quantity: ");
int enteredQuant = in.nextInt();
...and numerous other next methods, see Scanner
class in package java.util in Java 8 API!
*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!
OH -- and IS CONSIDERED GOOD PRACTICE to *close* your
Scanner instance when done!!
in.close();
*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!*!
**********
ANOTHER of SEVERAL ways to READ from a file in Java
**********
* (why "another"? Because we used one way in the JDBC examples,
to read the Oracle password from a file...)
* ...hey! you can also read from a FILE using
Scanner! (from package java.util)
Scanner inFile = new Scanner(new File("myFile.txt"));
* NOTICE: one of Scanner's constructors can take a *File*
class instance! That's what is being used above;
...and now I can use the Scanner methods to
read from this file myFile.txt in (probably) the
current working directory
String nextStr = inFile.nextLine();
* USEFUL: Scanner includes a hasNext method, that returns
true if there is still something left TO read --
so can use this to iterate through a file's
contents, reading them:
while (inFile.hasNext())
{
System.out.println(inFile.nextLine());
}
* GOOD STYLE: close your Scanner when done:
inFile.close();
**********
ONE of SEVERAL ways to WRITE to a file in Java
**********
* how about writing to a file?
package java.io has many output stream possibilities,
but we'll use PrintWriter
(then we have familiar methods println, print, printf!)
PrintWriter outStream = new PrintWriter("myFile.txt");
* note that PrintWriter has a constructor that just
expects a String representing the name of a file...
* then, amongst its methods are the familiar
println, print, printf...
...
outStream.println("blah");
outStream.printf("%.3f", 37.3);
...
* GOOD STYLE: close your PrintWriter when done:
outStream.close();
* ALSO NOTE: creating a PrintWriter may throw an exception
(what if you try to create an output stream to a file
that you aren't allowed to write to?) --
SO, may have to put this in a try-block!
(and remember that Exception should be a superclass or ancestor for
all exception classes, so can catch Exception if you want a
single complaint/action regardless of the specific exception
thrown...)
(BUT you can also check the Java 8 API, package java.io,
PrintWriter class, and go to the PrintWriter constructor
method to see what exceptions it MIGHT throw)
**********
ASIDE from AFTER CLASS
**********
* (notice: if you open an existing file on
a PrintWriter, it NUKES its current contents --
there IS a way to open for appending,
...with the help of FileWriter, which includes a constructor
whose second boolean argument determines whether the *file*
is (true) being opened for appending, with the next write
going at the end of the current contents, or
(false) being opening for writing, which nukes any present
contents,
...AND with the help of BufferedWriter, which
http://stackoverflow.com/questions/1625234/how-to-append-text-to-an-existing-file-in-java
recommends for use with an "expensive" writer such as FileWriter:
PrintWriter myAppendingOut =
new PrintWriter(new BufferedWriter(new FileWriter("filename.txt", true)));
* COULD just use FileWriter, but PrintWriter has the
print and println methods, which FileWriter does not;
you'd likely use the FileWriter's
write method in that case;
********* (end of appending-to-a-file aside) **********
**********
how to find out: what IS the current working directory?
(when a program is running)
**********
* what is your current working directory?
* from "Core Java" text, p. 84 (Chapter 3):
"When you specify a relative file name ... the file
is located relative to the directory in which the
Java Virtual Machine was started"
* IF you are using an IDE where it is unclear
WHERE the JVM is running --
(important because relative file names are
relative to the directory in which the JVM
was started)
you can find out what this is -- if needed/desired -- with:
String dir = System.getProperty("user.dir");
* (in a pinch, you can try absolute path names...)
********
ASIDE from Week 14 Lab (with thanks to P. Abbey!)
changing DrJava working directory on Windows:
********
If you are running DrJava on Windows, the following
should allow you to change the current working directory:
* From the Edit menu, select Preferences
* (a Preferences dialog should open up)
* From the Categories list on the left, select Interactions Pane
* Change the Interactions Working Directory to your desired
working directory
* click Apply button, then OK button
* (when you next compile, Interactions window should show
the newly-selected current working directory --
and files being read, written should be in this
directory...)
**********
TINY taste of Java collections/generics
[NOTE: added MORE EXAMPLE METHODS after class!]
**********
* references - from "Core Java" text:
* Chapter 5 - Section 3 - "generic ArrayLists"
* more on Java Collection classes in Chapter 13
* more on Java generics in Chapter 12
* Java Collections classes provide
MORE collection possibilities in addition to arrays!
* also many of these are generics -- they can be
collections of a specified type;
* C++ has its STL, Standard Template Library --
Java has its Collection classes,
implementations of a number of useful/classic
data structures;
* you can still build your own in Java, but you
can also use these!
* these are in package java.util
* Java added generics relatively recently --
(their version of C++ templates) --
you can have a collection type of instances
whose type YOU specify at declaration type;
(you put the type in angle brackets after the
generic type in the declaration -- as we'll soon see)
* here are SOME of the generic collection classes
in java.util:
* ArrayList - indexed, grows and shrinks dynamically,
BUT array-based
* LinkedList - an ordered (not necessarily contiguous)
linked sequential structure that allows
efficient insertions and removals at
any location
* HashSet - an unordered collection that rejects
duplicates
* ArrayDeque - a double-ended queue implemented
as a circular array
* TreeSet - a sorted set
* EnumSet - a set of enumerated type values
* HashMap - a data structure that stores key/value
associations
* PriorityQueue - a collection that allows efficient
removal of the smallest element
* ETC.
...Collection SHOULD be an ancestor Interface for
all of these;
(which means they share many common
methods!)
* *********
IMPORTANT note - these collections are collections
of objects; cannot have collections of primitive types
*********
* Let's play with ArrayList as an example of one
of these collection classes;
* it is generic class with a type parameter;
******
NOTE - Java generic classes, like C++ templates,
actually USE angle brackets AROUND the type
parameter!
...you DO actually type < and > around the
specified type after the generic collection name
*****
ArrayList<String> names = new ArrayList<String>();
ArrayList<GameDie> myDieSet = new ArrayList<GameDie>();
* then, you can add an element to the end of an ArrayList using
the add method:
names.add("George");
myDieSet.add(new GameDie(6));
myDieSet.add(new GameDie(10)); // now a 10-sided die at end
* you can get the element at a certain (0-based) position
using get:
names.get(0) // returns "George", given the examples so far
myDieSet.get(0).roll() // rolls the 6-sided die at position 0
// in myDieSet
myDieSet.get(0).getNumRolls() // would now return 1, given the
// examples so far
* you can find out its size -- how many elements it has --
using the size method:
names.size()
myDieSet.size()
* its 2-argument add method expects a 0-based position and the element
to be added, and adds it at that position (shifting the current element
there over) --
names.add(0, "Alice");
* now names contains "Alice" then "George"
* what if you want to REPLACE an element at a position?
use set, then:
names.set(0, "Carol");
* now names contains "Carol" then "George"
* oh, and you can remove an element at a given
(0-based) position:
names.add("Tommy");
names.remove(1); // removes element at position 1
// now names contains "Carol" then "Tommy"
...but you can also remove the FIRST instance of a
given object, too!
names.remove("Tommy");
// now names contains just "Carol"
* these collection classes often contain additional
useful methods --
ArrayList, for example, has
ensureCapacity(int)
...and then it will allocate an array of that
size, so that many adds WON'T necessitate
costly relocation;
can also specify an initial capacity with its
1-argument constructor:
ArrayList<Employee> staff = new ArrayList<Employee>(100);
trimToSize can be used to adjust the memory block
to JUST what is needed, when the size of an
ArrayList IS stable (but next add WILL be costly...)
etc.!
* and can use the "for each" loop with collections!
for (String aName: names)
{
System.out.println(aName + "!!");
}
**********
ASIDE after class: Iterator interface
**********
* a few words about Java iterators
* Iterator is a generic interface!
* provides/requires the methods
E next();
boolean hasNext();
void remove();
* You call next() repeatedly using the Iterator
object to "walk" through a collection --
BUT next() throws an exception if called when
there ISN'T a next element!
* SO, note: you use hasNext() to make SURE there's a next
element first...
* (Yes, Scanner DOES implement the interface
Iterator<String>... 8-) )