unless the code is written correctly from the start, the errors
must be found by testing the code
writing correct code is a lot of work, so most programmers use
testing to make their code as close to correct as possible
test routines can (and probably should) be included in the main
method of any class that doesn't already have one. This is a unit test.
This main method is a driver program. The driver program can
also be defined separately.
the unit test should call all the methods of the class, with
as many combinations of parameters as possible
if the writer of the test code doesn't study the code under test,
this is black box testing
at the very least, the goal of testing is full coverage: making
sure that every path through the code has been used at least once, and has
produced an acceptable result
to produce full coverage, the programmer of the test program must study
the code being tested: this is white box testing
A few common types of errors
off-by-one (fencepost): how many fence posts are needed for a fence
that is 20 feet long and has a post every 2 feet?
not initializing data correctly. Sometimes this causes null
pointer access
using different variables as if they were one, or using one variable
as if it were two variables
assumptions that don't turn out to be true (misconceptions),
not establishing and maintaining invariants
not checking things that should be checked, e.g. if (x == null)
testing strategies
print/show all method invocations and their parameters and return values
write code to check that the invariants are established and maintained
write test cases to not only provide full coverage, but also check
all boundary conditions, where the result should change (make sure it
changes where it should)
some common special cases:
< 0, 0, 1, > 1
first and last elements of an array, collection, linked list
elements and values that are null
desired element is not in the collection, or is in the collection
more than once
collection has size 0, 1, or larger
for example, when testing inserting on a linked list,
can test inserting at the beginning of a linked list,
at the end, in the middle, and inserting into an empty linked list. Also,
inserting an element that has the value null (is the behavior of
your program defined in that case? Should it be?)
if code to be tested needs to call a method that is not yet implemented,
a stub of that method can do only what is needed for the test
reasoning about programs
a precondition must be true before a method is called
the code in the method may (and usually will) assume that the
precondition is true
the caller of a method must guarantee (be sure) that the
precondition holds
a postcondition will be true after a method is called
the code in the method must guarantee that the postcondition
is true
preconditions and postconditions are a little bit like a
contract or any other agreement: if the caller provides the
preconditions, the method will provide the postconditions
preconditions and postconditions are documented in Javadoc
invariants are postconditions of every method, including the
constructors
invariants are preconditions of every method except the constructors
invariants are usually documented for the entire class rather than
for each method
Stacks
stacks of dishes or trays in a cafeteria
on a spring-loaded mechanism so only the top one is accessible
adding more dishes pushes down the stack, so only the new top
is still accessible
Last In First Out discipline (LIFO -- FIFO will be discussed at
a later lecture)
Stacks in computer science
a data structure to hold a variable number of elements
of which only the top one is accessible at any given time
the stack may be empty if it holds no elements
the stack keeps track of the number of elements it has,
as well as the individual elements
some stack implementations set a fixed maximum size for
the stack, so the stack overflows if more data is
added to a full stack
Stacks vs. arrays
an array has a fixed number of elements
an array does not keep track of which elements have been
initialized
any element of an array is equally easy to access
Stacks vs. linked lists
a linked list can access any element of the list
even though accessing some (esp. the head) is faster than
accessing others
in-class exercise (everyone together): what is the runtime (big O) of
empty(), push(), and pop()?
Other stack implementations, using a java Vector or List
any extensible data structure that has access at one end can be
used to implement a stack
this includes java Vectors and java Lists
new data is added to or removed from the end of the Vector or
ArrayList in O(1) time (when the array doesn't have to grow)
in the code in the book (p. 164/272), note that the data is
stored in an object of type List<E>, that is created
as an ArrayList<E>: this is an example of polymorphism,
and makes it easy to switch to a different kind of list
new data is added to or removed from the front of the Linked List,
in O(1) time