Tải bản đầy đủ (.pdf) (119 trang)

Thinking in Java 3rd Edition phần 9 doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (613.18 KB, 119 trang )


922 Thinking in Java www.BruceEckel.com
Once you have your Bean properly inside a JAR file you can bring it into a
Beans-enabled program-builder environment. The way you do this varies
from one tool to the next, but Sun provides a freely available test bed for
JavaBeans in their “Bean Builder.” (Download from
java.sun.com/beans.) You place a Bean into the Bean Builder by simply
copying the JAR file into the correct subdirectory.
Feedback

More complex Bean support
You can see how remarkably simple it is to make a Bean, but you aren’t
limited to what you’ve seen here. The JavaBeans architecture provides a
simple point of entry but can also scale to more complex situations. These
situations are beyond the scope of this book, but they will be briefly
introduced here. You can find more details at java.sun.com/beans.
Feedback

One place where you can add sophistication is with properties. The
examples above have shown only single properties, but it’s also possible to
represent multiple properties in an array. This is called an indexed
property. You simply provide the appropriate methods (again following a
naming convention for the method names) and the Introspector
recognizes an indexed property so your application builder tool can
respond appropriately.
Feedback

Properties can be bound, which means that they will notify other objects
via a PropertyChangeEvent. The other objects can then choose to
change themselves based on the change to the Bean.
Feedback



Properties can be constrained, which means that other objects can veto a
change to that property if it is unacceptable. The other objects are notified
using a PropertyChangeEvent, and they can throw a
PropertyVetoException to prevent the change from happening and to
restore the old values.
Feedback

You can also change the way your Bean is represented at design time:
Feedback

1. You can provide a custom property sheet for your particular Bean.
The ordinary property sheet will be used for all other Beans, but
yours is automatically invoked when your Bean is selected.
Feedback


Chapter 14: Creating Windows & Applets 923
2. You can create a custom editor for a particular property, so the
ordinary property sheet is used, but when your special property is
being edited, your editor will automatically be invoked.
Feedback

3. You can provide a custom BeanInfo class for your Bean that
produces information that’s different from the default created by
the Introspector.
Feedback

4. It’s also possible to turn “expert” mode on and off in all
FeatureDescriptors to distinguish between basic features and

more complicated ones.
Feedback

More to Beans
There are a number of books about JavaBeans; for example, JavaBeans
by Elliotte Rusty Harold (IDG, 1998).
Feedback

Summary
Of all the libraries in Java, the GUI library has seen the most dramatic
changes from Java 1.0 to Java 2. The Java 1.0 AWT was roundly criticized
as being one of the worst designs seen, and while it would allow you to
create portable programs, the resulting GUI was “equally mediocre on all
platforms.” It was also limiting, awkward, and unpleasant to use
compared with the native application development tools available on a
particular platform.
Feedback

When Java 1.1 introduced the new event model and JavaBeans, the stage
was set—now it was possible to create GUI components that could be
easily dragged and dropped inside visual application builder tools. In
addition, the design of the event model and JavaBeans clearly shows
strong consideration for ease of programming and maintainable code
(something that was not evident in the 1.0 AWT). But it wasn’t until the
JFC/Swing classes appeared that the job was finished. With the Swing
components, cross-platform GUI programming can be a civilized
experience.
Feedback

Actually, the only thing that’s missing is the application builder tool, and

this is where the real revolution lies. Microsoft’s Visual Basic and Visual
C++ require Microsoft’s application builder tools, as does Borland’s

924 Thinking in Java www.BruceEckel.com
Delphi and C++ Builder. If you want the application builder tool to get
better, you have to cross your fingers and hope the vendor will give you
what you want. But Java is an open environment, and so not only does it
allow for competing application builder environments, it encourages
them. And for these tools to be taken seriously, they must support
JavaBeans. This means a leveled playing field: if a better application
builder tool comes along, you’re not tied to the one you’ve been using—
you can pick up and move to the new one and increase your productivity.
This kind of competitive environment for GUI application builder tools
has not been seen before, and the resulting marketplace can generate only
positive results for the productivity of the programmer.
Feedback

This chapter was meant only to give you an introduction to the power of
Swing and to get you started so you could see how relatively simple it is to
feel your way through the libraries. What you’ve seen so far will probably
suffice for a good portion of your UI design needs. However, there’s a lot
more to Swing—it’s intended to be a fully powered UI design tool kit.
There’s probably a way to accomplish just about everything you can
imagine.
Feedback

If you don’t see what you need here, delve into the JDK documentation
from Sun and search the Web, and if that’s not enough then find a
dedicated Swing book—a good place to start is The JFC Swing Tutorial,
by Walrath & Campione (Addison Wesley, 1999).

Feedback

Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in Java
Annotated Solution Guide, available for a small fee from www.BruceEckel.com.

1. Create an applet/application using the Console class as shown in
this chapter. Include a text field and three buttons. When you
press each button, make some different text appear in the text
field.
Feedback

2. Add a check box to the applet created in Exercise 1, capture the
event, and insert different text into the text field.
Feedback

3. Create an applet/application using Console. In the JDK
documentation from java.sun.com, find the JPasswordField

Chapter 14: Creating Windows & Applets 925
and add this to the program. If the user types in the correct
password, use Joptionpane to provide a success message to the
user.
Feedback

4. Create an applet/application using Console, and add all the
Swing components that have an addActionListener( ) method.
(Look these up in the JDK documentation from java.sun.com.
Hint: use the index.) Capture their events and display an
appropriate message for each inside a text field.

Feedback

5. Create an applet/application using Console, with a JButton and
a JTextField. Write and attach the appropriate listener so that if
the button has the focus, characters typed into it will appear in the
JTextField.
Feedback

6. Create an applet/application using Console. Add to the main
frame all the components described in this chapter, including
menus and a dialog box.
Feedback

7. Modify TextFields.java so that the characters in t2 retain the
original case that they were typed in, instead of automatically
being forced to upper case.
Feedback

8. Locate and download one or more of the free GUI builder
development environments available on the Internet, or buy a
commercial product. Discover what is necessary to add
BangBean to this environment and to use it.
Feedback

9. Add Frog.class to the manifest file as shown in this chapter and
run jar to create a JAR file containing both Frog and BangBean.
Now either download and install the Bean Builder from Sun or use
your own Beans-enabled program builder tool and add the JAR
file to your environment so you can test the two Beans.
Feedback


10. Create your own JavaBean called Valve that contains two
properties: a boolean called “on” and an int called “level.” Create
a manifest file, use jar to package your Bean, then load it into the
Bean Builder or into a Beans-enabled program builder tool so that
you can test it.
Feedback


926 Thinking in Java www.BruceEckel.com
11. Modify MessageBoxes.java so that it has an individual
ActionListener for each button (instead of matching the button
text).
Feedback

12. Monitor a new type of event in TrackEvent.java by adding the
new event handling code. You’ll need to discover on your own the
type of event that you want to monitor.
Feedback

13. Inherit a new type of button from JButton. Each time you press
this button, it should change its color to a randomly-selected
value. See ColorBoxes.java for an example of how to generate a
random color value.
Feedback

14. Modify TextPane.java to use a JTextArea instead of a
JTextPane.
Feedback


15. Modify Menus.java to use radio buttons instead of check boxes
on the menus.
Feedback

16. Simplify List.java by passing the array to the constructor and
eliminating the dynamic addition of elements to the list.
Feedback

17. Modify SineWave.java to turn SineDraw into a JavaBean by
adding “getter” and “setter” methods.
Feedback

18. Remember the “sketching box” toy with two knobs, one that
controls the vertical movement of the drawing point, and one that
controls the horizontal movement? Create one of those, using
SineWave.java to get you started. Instead of knobs, use sliders.
Add a button that will erase the entire sketch.
Feedback

19. Starting with SineWave.java, create a program (an
applet/application using the Console class) that draws an
animated sine wave that appears to scroll past the viewing window
like an oscilloscope, driving the animation with a Thread. The
speed of the animation should be controlled with a
java.swing.JSlider control.
Feedback

20. Modify Exercise 19 so that multiple sine wave panels are created
within the application. The number of sine wave panels should be
controlled by HTML tags or command-line parameters.

Feedback


Chapter 14: Creating Windows & Applets 927
21. Modify Exercise 19 so that the java.swing.Timer class is used to
drive the animation. Note the difference between this and
java.util.Timer.
Feedback

22. Create an “asymptotic progress indicator” that gets slower and
slower as it approaches the finish point. Add random erratic
behavior so it will periodically look like it’s starting to speed up.
Feedback

23. Modify Progress.java so that it does not share models, but
instead uses a listener to connect the slider and progress bar.
Feedback

24. Follow the instructions in the section titled “Packaging an applet
into a JAR file” to place TicTacToe.java into a JAR file. Create
an HTML page with the simple version of the applet tag along with
the archive specification to use the JAR file. Run
HTMLconverter on file to produce a working HTML file.
Feedback

25. Create an applet/application using Console. This should have
three sliders, one each for the red, green, and blue values in
java.awt.Color. The rest of the form should be a JPanel that
displays the color determined by the three sliders. Also include
non-editable text fields that show the current RGB values.

Feedback

26. In the JDK documentation for javax.swing, look up the
JColorChooser. Write a program with a button that brings up
the color chooser as a dialog.
Feedback

27. Almost every Swing component is derived from Component,
which has a setCursor( ) method. Look this up in the JDK
documentation. Create an applet and change the cursor to one of
the stock cursors in the Cursor class.
Feedback

28. Starting with ShowAddListeners.java, create a program with
the full functionality of c10:ShowMethods.java.
Feedback

29. Turn c12:TestRegularExpression.java into an interactive
Swing program that allows you to put an input string in one
TextArea and a regular expression in a TextField. The results
should be displayed in a second TextArea.

928 Thinking in Java www.BruceEckel.com
30. Modify InvokeLaterFrame.java to use invokeAndWait( ).


929
15: Discovering
problems
Before C was tamed into ANSI C, we had a little joke: “my

code compiles, so it should run!” (Ha ha!).
This was funny only if you understood C, because at that time the C
compiler would accept just about anything—C was truly a “portable
assembly language,” created to see if it was possible to develop a portable
operating system (Unix) that could be moved from one machine
architecture to another without rewriting it from scratch in the new
machine’s assembly language. So C was actually created as a side effect of
building Unix, and not as a general-purpose programming language.
Feedback

Because C was targeted at programmers who wrote operating systems in
assembly language, it was implicitly assumed that those programmers
knew what they were doing and didn’t need safety nets. For example,
assembly-language programmers didn’t need the compiler to check
argument types and usage, and if they decided to use a data type in a
different way than it was originally intended, they certainly must have
good reason to do so, and the compiler didn’t get in the way. Thus, getting
your pre-ANSI C program to compile was only the first step in the long
process of developing a bug-free program.
Feedback

The development of ANSI C along with stronger rules about what the
compiler would accept came after lots of people used C for projects other
than writing operating systems, and after the appearance of C++ which
greatly improved your chances of having a program run decently once it
compiled. Much of this improvement came through strong static type
checking: “strong,” because the compiler prevented you from abusing the
type, “static” because ANSI C and C++ perform type checking at compile
time.
Feedback



930 Thinking in Java www.BruceEckel.com
To many people (myself included), the improvement was so dramatic that
it appeared that strong static type checking was the answer to a large
portion of our problems. Indeed, one of the motivations for Java was that
C++’s type checking wasn’t strong enough (primarily because C++ had to
be backward-compatible with C, and so was chained to its limitations).
Thus Java has gone even further to take advantage of the benefits of type
checking, and since Java has language-checking mechanisms that exist at
run time (C++ doesn’t; what’s left at run time is basically assembly
language—very fast, but with no self-awareness) it isn’t restricted to only
static type checking
1
.
Feedback

It seems, however, that language-checking mechanisms can take us only
so far in our quest to develop a correctly-working program. C++ gave us
programs that worked a lot sooner than C programs, but often still had
problems like memory leaks and subtle, buried bugs. Java went a long
way towards solving those problems, and yet it’s still quite possible to
write a Java program containing nasty bugs. In addition (despite the
amazing performance claims always touted by the flaks at Sun) all the
safety nets in Java added additional overhead, so sometimes we run into
the challenge of getting our Java programs to run fast enough for a
particular need (although it’s usually more important to have a working
program than one that runs at a particular speed).
Feedback


This chapter presents tools to solve the problems that the compiler
doesn’t. In a sense, we are admitting that the compiler can only take us so
far in the creation of robust programs, and so we are moving beyond the
compiler and creating a build system and code that know more about
what a program is and isn’t supposed to do.
Feedback



1
It is primarily oriented to static checking, however. There is an alternative system, called
latent typing or dynamic typing or weak typing, in which the type of an object is still
enforced, but it is enforced at run time, when the type is used, rather than at compile time.
Writing code in such a language—Python () is an excellent
example—gives the programmer much more flexibility and requires far less verbiage to
satisfy the compiler, and yet still guarantees that objects are used properly. However, to a
programmer convinced that strong, static type checking is the only sensible solution,
latent typing is anathema and serious flame wars have resulted from comparisons between
the two approaches. As someone who is always in pursuit of greater productivity, I have
found the value of latent typing to be very compelling. In addition, the ability to think
about the issues of latent typing help you, I believe, to solve problems that are difficult to
think about in strong, statically typed languages.

Chapter 15: Discovering problems 931
One of the biggest steps forward is the incorporation of automated unit
testing. This means writing tests and incorporating those tests into a build
system that compiles your code and runs the tests every single time, as if
the tests were part of the compilation process (you’ll soon start relying
upon them as if they are). For this book, a custom testing system was
developed to ensure the correctness of the program output (and to display

the output directly in the code listing), but the defacto standard JUnit
testing system will also be used when appropriate. To make sure that
testing is automatic, tests are run as part of the build process using Ant,
an open-source tool that has also become a defacto standard in Java
development, and CVS, another open-source tool that maintains a
repository containing all your source code for a particular project.
Feedback

JDK 1.4 introduced an assertion mechanism to aid in the verification of
code at run time. One of the more compelling uses of assertions is Design
by Contract (DBC), a formalized way to describe the correctness of a
class. In conjunction with automated testing, DBC can be a powerful tool.
Feedback

Sometimes unit testing isn’t enough, and you need to track down
problems in a program that runs, but doesn’t run right. In JDK 1.4, the
logging API was introduced to allow you to easily report information
about your program. This is a significant improvement over adding and
removing println( ) statements in order to track down a problem, and
this section will go into enough detail to give you a thorough grounding in
this API. This chapter also provides an introduction to debugging,
showing the information a typical debugger can provide to aid you in the
discovery of subtle problems. Finally, you’ll learn about profiling and how
to discover the bottlenecks that cause your program to run too slowly.
Feedback

Unit Testing
A recent realization in programming practice is the dramatic value of unit
testing. This is the process of building integrated tests into all the code
that you create, and running those tests every time you do a build. That

way, the build process can check for more than just syntax errors, since
you teach it how to check for semantic errors as well. C-style

932 Thinking in Java www.BruceEckel.com
programming languages, and C++ in particular, have typically valued
performance over programming safety. The reason that developing
programs in Java is so much faster than in C++ (roughly twice as fast, by
most accounts) is because of Java’s safety net: features like garbage
collection and improved type checking. By integrating unit testing into
your build process, you can extend this safety net, resulting in faster
development. You can also be bolder in the changes that you make, more
easily refactor your code when you discover design or implementation
flaws, and in general produce a better product, faster.
Feedback

The effect of unit testing on development is so significant that it is used
throughout this book, not only to validate the code in the book but also to
display the expected output. My own experience with unit testing began
when I realized that, to guarantee the correctness of code in a book, every
program in that book must be automatically extracted and organized into
a source tree, along with an appropriate build system. The build system
used in this book is Ant (described later in this chapter), and after you
install it you can just type ant to build all the code for the book. The effect
of the automatic extraction and compilation process on the code quality of
the book was so immediate and dramatic that it soon became (in my
mind) a requisite for any programming book—how can you trust code that
you didn’t compile? I also discovered that if I wanted to make sweeping
changes, I could do so using search-and-replace throughout the book or
just by bashing the code around. I knew that if I introduced a flaw, the
code extractor and the build system would flush it out.

Feedback

As programs became more complex, however, I also found that there was
a serious hole in my system. Being able to successfully compile programs
is clearly an important first step, and for a published book it seems a fairly
revolutionary one—usually due to the pressures of publishing, it’s quite
typical to randomly open a programming book and discover a coding flaw.
However, I kept getting messages from readers reporting semantic
problems in my code. These problems could only be discovered by
running the code. Naturally, I understood this and took some early
faltering steps towards implementing a system that would perform
automatic execution tests, but I had succumbed to publishing schedules,
all the while knowing that there was definitely something wrong with my

Chapter 15: Discovering problems 933
process and that it would come back to bite me in the form of
embarrassing bug reports (in the open source world
2
, embarrassment is
one of the prime motivating factors towards increasing the quality of one’s
code!).
Feedback

The other problem was that I was lacking a structure for the testing
system. Eventually, I started hearing about unit testing and JUnit, which
provided a basis for a testing structure. I found the initial versions of
JUnit to be intolerable because they required the programmer to write too
much code in order to create even the simplest test suite. More recent
versions have significantly reduced this required code by using reflection,
and so are much more satisfactory.

Feedback

I needed to solve another problem, however, and that was to validate the
output of a program, and to show the validated output in the book. I had
gotten regular complaints that I didn’t show enough program output in
the book. My attitude was that the reader should be running the programs
while reading the book, and many readers did just that and benefited
from it. A hidden reason for that attitude, however, was that I didn’t have
a way to test that the output shown in the book was correct. From
experience, I knew that over time, something would happen so that the
output was no longer correct (or, I wouldn’t get it right in the first place).
The simple testing framework shown here not only captures the console
output of the program—and most programs in this book produce console
output—but it also compares it to the expected output which is printed in
the book as part of the source-code listing, so readers can see what the
output will be and also know that this output has been verified by the
build process, and that they can verify it themselves.
Feedback

I wanted to see if the test system could be even easier and simpler to use,
applying the Extreme Programming principle of “do the simplest thing
that could possibly work” as a starting point, and then evolving the system
as usage demands (In addition, I wanted to try to reduce the amount of
test code, in an attempt to fit more functionality in less code for screen


2
Although the electronic version of this book is freely available, it is not open source.

934 Thinking in Java www.BruceEckel.com

presentations). The result
3
is the simple testing framework described
next.
Feedback

A Simple Testing Framework
The primary goal of this framework
4
is to verify the output of the
examples in the book. You have already seen lines such as
private static Test monitor = new Test();

at the beginning of most classes that contain a main( ) method. The task
of the monitor object is to intercept and save a copy of standard output
and standard error into a text file. This file is then used to verify the
output of an example program, by comparing the contents of the file to
the expected output.
Feedback
We start by defining the exceptions that will be thrown by this test system.
The general-purpose exception for the library is the base class for the
others. Note that it extends RuntimeException so that checked
exceptions are not involved:
//: com:bruceeckel:simpletest:SimpleTestException.java
package com.bruceeckel.simpletest;

public class SimpleTestException extends RuntimeException {
public SimpleTestException(String msg) {
super(msg);
}

} ///:~

A basic test is to verify that the number of lines sent to the console by the
program is the same as the expected number of lines:
//: com:bruceeckel:simpletest:NumOfLinesException.java
package com.bruceeckel.simpletest;

public class NumOfLinesException

3
The first try, anyway. I find that the process of building something for the first time
eventually produces insights and new ideas.
4
Inspired by Python’s doctest module.

Chapter 15: Discovering problems 935
extends SimpleTestException {
public NumOfLinesException(int exp, int out) {
super("Number of lines of output and "
+ "expected output did not match.\n" +
"expected: <" + exp + ">\n" +
"output: <" + out + "> lines)");
}
} ///:~

Or, the number of lines might be correct, but one or more lines might not
match:
//: com:bruceeckel:simpletest:LineMismatchException.java
package com.bruceeckel.simpletest;
import java.io.PrintStream;


public class LineMismatchException
extends SimpleTestException {
public LineMismatchException(
int lineNum, String expected, String output) {
super("line " + lineNum +
" of output did not match expected output\n" +
"expected: <" + expected + ">\n" +
"output: <" + output + ">");
}
} ///:~

This test system works by intercepting the console output using the
TestStream class to replace the standard console output and console
error:
//: com:bruceeckel:simpletest:TestStream.java
// Simple utility for testing program output. Intercepts
// System.out to print both to the console and a buffer.
package com.bruceeckel.simpletest;
import java.io.*;
import java.util.*;
import java.util.regex.*;

public class TestStream extends PrintStream {
protected int numOfLines;
private PrintStream
console = System.out,
err = System.err,
fout;


936 Thinking in Java www.BruceEckel.com
// To store lines sent to System.out or err
private InputStream stdin;
private String className;
public TestStream(String className) {
super(System.out, true); // Autoflush
System.setOut(this);
System.setErr(this);
stdin = System.in; // Save to restore in dispose()
// Replace the default version with one that
// automatically produces input on demand:
System.setIn(new BufferedInputStream(new InputStream(){
char[] input = ("test\n").toCharArray();
int index = 0;
public int read() {
return
(int)input[index = (index + 1) % input.length];
}
}));
this.className = className;
openOutputFile();
}
// public PrintStream getConsole() { return console; }
public void dispose() {
System.setOut(console);
System.setErr(err);
System.setIn(stdin);
}
// This will write over an old Output.txt file:
public void openOutputFile() {

try {
fout = new PrintStream(new FileOutputStream(
new File(className + "Output.txt")));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
// Override all possible print/println methods to send
// intercepted console output to both the console and
// the Output.txt file:
public void print(boolean x) {
console.print(x);
fout.print(x);
}
public void println(boolean x) {

Chapter 15: Discovering problems 937
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(char x) {
console.print(x);
fout.print(x);
}
public void println(char x) {
numOfLines++;
console.println(x);
fout.println(x);
}

public void print(int x) {
console.print(x);
fout.print(x);
}
public void println(int x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(long x) {
console.print(x);
fout.print(x);
}
public void println(long x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(float x) {
console.print(x);
fout.print(x);
}
public void println(float x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(double x) {
console.print(x);
fout.print(x);

}

938 Thinking in Java www.BruceEckel.com
public void println(double x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(char[] x) {
console.print(x);
fout.print(x);
}
public void println(char[] x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(String x) {
console.print(x);
fout.print(x);
}
public void println(String x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void print(Object x) {
console.print(x);
fout.print(x);
}

public void println(Object x) {
numOfLines++;
console.println(x);
fout.println(x);
}
public void println() {
if(false) console.print("println");
numOfLines++;
console.println();
fout.println();
}
public void
write(byte[] buffer, int offset, int length) {
console.write(buffer, offset, length);
fout.write(buffer, offset, length);
}
public void write(int b) {

Chapter 15: Discovering problems 939
console.write(b);
fout.write(b);
}
} ///:~

The constructor for TestStream, after calling the constructor for the
base class, first saves references to standard output and standard error,
and then redirects both streams to the TestStream object. The static
methods setOut( ) and setErr( ) both take a PrintStream argument.
System.out and System.err references are unplugged from their
normal object and instead are plugged into the TestStream object, so

TestStream must also be a PrintStream (or equivalently, something
inherited from PrintStream). The original standard output
PrintStream reference is captured in the console reference inside
TestStream, and every time console output is intercepted, it is sent to
the original console as well as to an output file. The dispose( ) method is
used to set standard I/O references back to their original objects when
TestStream is finished with them.
Feedback

For automatic testing of examples that require user input from the
console, the constructor redirects calls to standard input. The current
standard input is stored in a reference so that dispose( ) can restore it to
its original state. Using System.setIn( ), an anonymous inner class is set
to handle any requests for input by the program under test. The read( )
method of this inner class produces the letters “test” followed by a
newline.
Feedback

TestStream overrides a variety of PrintStream print( ) and
println( ) methods for each type. Each of these methods writes both to
the “standard” output and to an output file. The expect( ) method can
then be used to test whether output produced by a program matches the
expected output provided as argument to expect( ).
Feedback

These tools are used in the Test class:
//: com:bruceeckel:simpletest:Test.java
// Simple utility for testing program output. Intercepts
// System.out to print both to the console and a buffer.
package com.bruceeckel.simpletest;

import java.io.*;
import java.util.*;

940 Thinking in Java www.BruceEckel.com
import java.util.regex.*;

public class Test {
// Bit-shifted so they can be added together:
public static final int
EXACT = 1 << 0, // Lines must match exactly
AT_LEAST = 1 << 1, // Must be at least these lines
IGNORE_ORDER = 1 << 2, // Ignore line order
WAIT = 1 << 3; // Delay until all lines are output
private String className;
private TestStream testStream;
public Test() {
// Discover the name of the class this
// object was created within:
className =
new Throwable().getStackTrace()[1].getClassName();
testStream = new TestStream(className);
}
public static List fileToList(String fname) {
ArrayList list = new ArrayList();
try {
BufferedReader in =
new BufferedReader(new FileReader(fname));
try {
String line;
while((line = in.readLine()) != null) {

if(fname.endsWith(".txt"))
list.add(line);
else
list.add(new TestExpression(line));
}
} finally {
in.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return list;
}
public static List arrayToList(Object[] array) {
List l = new ArrayList();
for(int i = 0; i < array.length; i++) {
if(array[i] instanceof TestExpression) {
TestExpression re = (TestExpression)array[i];

Chapter 15: Discovering problems 941
for(int j = 0; j < re.getNumber(); j++)
l.add(re);
} else {
l.add(new TestExpression(array[i].toString()));
}
}
return l;
}
public void expect(Object[] exp, int flags) {
if((flags & WAIT) != 0)

while(testStream.numOfLines < exp.length) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
List output = fileToList(className + "Output.txt");
if((flags & IGNORE_ORDER) == IGNORE_ORDER)
OutputVerifier.verifyIgnoreOrder(output, exp);
else if((flags & AT_LEAST) == AT_LEAST)
OutputVerifier.verifyAtLeast(output,
arrayToList(exp));
else
OutputVerifier.verify(output, arrayToList(exp));
// Clean up the output file - see c06:Detergent.java
testStream.openOutputFile();
}
public void expect(Object[] expected) {
expect(expected, EXACT);
}
public void expect(Object[] expectFirst,
String fname, int flags) {
List expected = fileToList(fname);
for(int i = 0; i < expectFirst.length; i++)
expected.add(i, expectFirst[i]);
expect(expected.toArray(), flags);
}
public void expect(Object[] expectFirst, String fname) {
expect(expectFirst, fname, EXACT);

}
public void expect(String fname) {
expect(new Object[] {}, fname, EXACT);
}

942 Thinking in Java www.BruceEckel.com
} ///:~

There are several overloaded versions of expect( ) provided for
convenience (so the client programmer can, for example, provide the
name of the file containing the expected output instead of an array of
expected output lines). These overloaded methods all call the main
expect( ) method, which takes as arguments an array of Objects
containing expected output lines and an int containing various flags.
Flags are implemented using bit shifting, with each bit corresponding to a
particular flag as defined at the beginning of Test.java.
Feedback

The expect( ) method first inspects the flags argument to see if it should
delay processing to allow a slow program to catch up. It then calls a static
method fileToList( ), which converts the contents of the output file
produced by a program into a List. The fileToList( ) method also wraps
each String object in an OutputLine object; the reason for this will
become clear. Finally, the expect( ) method calls the appropriate
verify( ) method based on the flags argument.
Feedback

There are three verifiers: verify( ), verifyIgnoreOrder( ), and
verifyAtLeast( ), corresponding to EXACT, IGNORE_ORDER, and
AT_LEAST modes, respectively:

//: com:bruceeckel:simpletest:OutputVerifier.java
package com.bruceeckel.simpletest;
import java.util.*;
import java.io.PrintStream;

public class OutputVerifier {
private static void verifyLength(
int output, int expected, int compare) {
if((compare == Test.EXACT && expected != output)
|| (compare == Test.AT_LEAST && output < expected))
throw new NumOfLinesException(expected, output);
}
public static void verify(List output, List expected) {
verifyLength(output.size(),expected.size(),Test.EXACT);
if(!expected.equals(output)) {
//find the line of mismatch
ListIterator it1 = expected.listIterator();
ListIterator it2 = output.listIterator();
while(it1.hasNext()
&& it2.hasNext()

Chapter 15: Discovering problems 943
&& it1.next().equals(it2.next()));
throw new LineMismatchException(
it1.nextIndex(), it1.previous().toString(),
it2.previous().toString());
}
}
public static void
verifyIgnoreOrder(List output, Object[] expected) {

verifyLength(expected.length,output.size(),Test.EXACT);
if(!(expected instanceof String[]))
throw new RuntimeException(
"IGNORE_ORDER only works with String objects");
String[] out = new String[output.size()];
Iterator it = output.iterator();
for(int i = 0; i < out.length; i++)
out[i] = it.next().toString();
Arrays.sort(out);
Arrays.sort(expected);
int i =0;
if(!Arrays.equals(expected, out)) {
while(expected[i].equals(out[i])) {i++;}
throw new SimpleTestException(
((String) out[i]).compareTo(expected[i]) < 0
? "output: <" + out[i] + ">"
: "expected: <" + expected[i] + ">");
}
}
public static void
verifyAtLeast(List output, List expected) {
verifyLength(output.size(), expected.size(),
Test.AT_LEAST);
if(!output.containsAll(expected)) {
ListIterator it = expected.listIterator();
while(output.contains(it.next())) {}
throw new SimpleTestException(
"expected: <" + it.previous().toString() + ">");
}
}

} ///:~

The “verify” methods test whether the output produced by a program
matches the expected output as specified by the particular mode. If this is
not the case, the “verify” methods raise an exception that aborts the build
process.
Feedback


944 Thinking in Java www.BruceEckel.com
Each of the “verify” methods uses verifyLength( ) to test the number of
lines of output. EXACT mode requires that both output and expected
output arrays be the same size, and that each output line is equal to the
corresponding line in the expected output array. IGNORE_ORDER still
requires that both arrays be the same size, but the actual order of
appearance of the lines is disregarded (the two output arrays must be
permutations of one another). IGNORE_ORDER mode is used to test
threading examples where, due to non-deterministic scheduling of
threads by the JVM, it is possible that the sequence of output lines
produced by a program cannot be predicted. AT_LEAST mode does not
require the two arrays to be the same size, but each line of expected
output must be contained in the actual output produced by a program,
regardless of order. This feature is particularly useful for testing program
examples which contain output lines that may or may not be printed, as is
the case with most of the examples dealing with garbage collection. Notice
that the three modes are canonical; that is, if a test passes in
IGNORE_ORDER mode, then it will also pass in AT_LEAST mode,
and if it passes in EXACT mode, it will also pass in the other two modes.
Feedback
Notice how simple the implementation of the “verify” methods is.

verify( ), for example, simply calls the equals( ) method provided by the
List class, and verifyAtLeast( ) calls List.containsAll( ). Remember
that the two output Lists can contain both OutputLine or
RegularExpression objects. The reason for wrapping the simple
String object in OutputLines should now become clear: this approach
allows us to override the equals( ) method, which is necessary in order to
take advantage of the Java Collections API.
Feedback

Objects in the expect( ) array can be either Strings or
TestExpressions, which can encapsulate a regular expression
(described in Chapter 14), which is useful for testing examples that
produce random output. The TestExpression class encapsulates a
String representing a particular regular expression.
Feedback

//: com:bruceeckel:simpletest:TestExpression.java
// Regular expression for testing program output lines
package com.bruceeckel.simpletest;
import java.util.regex.*;


Chapter 15: Discovering problems 945
public class TestExpression implements Comparable {
private Pattern p;
private String expression;
private boolean isRegEx;
// Default to only one instance of this expression:
private int duplicates = 1;
public TestExpression(String s) {

this.expression = s;
if(expression.startsWith("%% ")) {
this.isRegEx = true;
expression = expression.substring(3);
this.p = Pattern.compile(expression);
}
}
// For duplicate instances:
public TestExpression(String s, int duplicates) {
this(s);
this.duplicates = duplicates;
}
public String toString() {
if(isRegEx) return p.pattern();
return expression;
}
public boolean equals(Object obj) {
if(this == obj) return true;
if(isRegEx) return (compareTo(obj) == 0);
return expression.equals(obj.toString());
}
public int compareTo(Object obj) {
if((isRegEx) && (p.matcher(obj.toString()).matches()))
return 0;
return
expression.compareTo(obj.toString());
}
public int getNumber() { return duplicates; }
public String getExpression() { return expression;}
public boolean isRegEx() { return isRegEx; }

} ///:~

TestExpression can distinguish regular expression patterns from
String literals. The second constructor allows multiple identical
expression lines to be wrapped in a single object for convenience.
Feedback

946 Thinking in Java www.BruceEckel.com
This test system has been reasonably useful, and the exercise of creating it
and putting it into use has been invaluable. However, in the end I’m not
that pleased with it and have ideas that will probably be implemented in
the next edition of the book (or possibly sooner).
Feedback

JUnit
Although the testing framework just described allows you to simply and
easily verify program output, in some cases you may want to perform
more extensive functionality testing on a program. JUnit, available at
, is a quickly emerging standard for writing
repeatable tests for Java programs, and provides both simple and
complex testing.
Feedback

The original JUnit was presumably based on JDK 1.0 and thus could not
make use of Java’s reflection facilities. As a result, writing unit tests with
the old JUnit was a rather busy and wordy activity, and I found the design
to be unpleasant. Because of this, I wrote my own unit testing framework
for Java
5
, going to the other extreme and “doing the simplest thing that

could possibly work
6
.” Since then, JUnit has been modified and uses
reflection to greatly simplify the process of writing unit test code.
Although you still have the option of writing code the “old” way with test
suites and all the other complex details, I believe that in the great majority
of cases you can follow the simple approach shown here (and make your
life more pleasant).
Feedback

In the simplest approach to using JUnit, you put all your tests in a
subclass of TestCase. Each test must be public, take no arguments,
return void, and have a method name beginning with the word “test.”
JUnit’s reflection will identify these methods as individual tests, and set
up and run them one at a time, taking measures to avoid side effects
between the tests.
Feedback



5
Originally placed in Thinking in Patterns with Java at www.BruceEckel.com. However,
with the addition of the reflection approach in JUnit, my framework doesn’t make much
sense anymore and will probably be removed.
6
A key phrase from Extreme Programming (XP). Ironically, one of the JUnit authors
(Kent Beck) is also the author of Extreme Programming Explained (Addison-Wesley
2000) and a main proponent of XP.

×