Tools: the Command Line, Checkstyle, and JUnit

The machinery the main text kept out of the way: what a JDK and a JVM actually are, the four command-line commands worth learning first, redirecting a test file into System.in so you never retype input again, Checkstyle, a debugger's breakpoints and call stack, and JUnit's assertEquals. Follows Think Java 2e, Chapter A (Tools), Sections A.1-A.8, pp. 297-307, cross-referenced against The Java Tutorials — The javac and java Commands.

Subject: Java · 65 slides · code lesson

Open the interactive version of this deck

What this lesson covers

The lesson, slide by slide

1. Tools: the Command Line, Checkstyle, and JUnit

Title

Think Java 2e · Chapter A · Tools

Sections A.1-A.8 · pp. 297-307

2. What you will be able to do

Objectives

This lesson follows Think Java 2e, Chapter A (Tools), Sections A.1-A.8, pp. 297-307. Everything on these slides can be checked against those pages.

1. Distinguish the JDK, the JVM, an IDE and a text editor.

2. Compile and run a Java program from the command line, and say what each command needs.

3. Automate a test using redirection operators and diff.

4. Explain what a style checker can and cannot evaluate.

5. Use a debugger's breakpoints and call stack to trace execution.

6. Write a JUnit test class and explain the argument order of assertEquals.

3. Retrieve before you read

Warm-up

Three things from the very first chapters, now seen from underneath.

Discussion prompt

From Lesson 1a: what does the compiler produce, and what runs it? From Lesson 4b: what is incremental development? And from Lesson 4a: what is a stack diagram?

Hint: Byte code, and a picture of the frames.

Answer:

The compiler produces byte code, which the Java Virtual Machine interprets. Incremental development means adding and testing a few lines at a time. And a stack diagram shows the chain of method calls with their parameters.

This appendix gives you the tools behind all three. The steps for compiling, running, and debugging Java code depend on your development environment and operating system. We avoided putting these details in the main text, because they can be distracting.

4. What you actually need to run Java

Concept

If you want to compile and run Java programs on your own computer, you will need the following — and it is worth knowing which piece does what.

Figure (svg): A pipeline from source code through the compiler to byte code and the virtual machine

Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter A (Tools), Sections A.1-A.8, pp. 297-307 — Appendix A begins on printed page 297.

5. The pieces of a Java setup

Section

Sections A.1-A.2

6. Four things, and only one of them is required

Concept

The easiest way to start programming in Java is to use a website that compiles and runs Java code in the browser. If you are unable to install software — which is often the case in public schools and Internet cafés — an online environment covers almost everything in this book.

JDK — The Java Development Kit, which contains the compiler, Javadoc, and other tools.

JVM — The Java Virtual Machine, which interprets the compiled byte code.

IDE — An integrated development environment that includes tools for editing, compiling, and debugging programs.

text editor — A program that edits plain text files, the format used by most programming languages.

piecewhat it isneeded?
JDKthe compiler, the JVM, and tools like Javadocyes, to compile locally
JVMinterprets the compiled byte codeyes — it is inside the JDK
text editoredits plain text filesyes, in some form
IDEediting, compiling and debugging in one programno — convenient, not required

A text editor such as Atom, Notepad++, or Sublime Text, and/or an IDE such as DrJava, Eclipse, jGrasp, or NetBeans. The book recommends OpenJDK, an open source implementation of Java SE, and DrJava as a beginner's IDE — but every idea in this appendix applies to whichever tools you end up using.

7. What a JAR file is

Notation

Two of this appendix's tools ship as JAR files, and the vocabulary entry explains why that is convenient.

Annotate

  • A JAR is a ZIP file with a particular layout — you could open one with an unzip tool and find .class files inside.
  • It packages a whole program into one file, which is why distributing Java software usually means distributing a JAR.
  • java -jar runs one, using a main class recorded inside the archive.
  • Resources travel with it too. Checkstyle's /google_checks.xml is inside the JAR file, which is why the path begins with a slash.
  • Your own compiled classes could be packaged the same way — a JAR is not special to library authors.

A single file that contains an entire program and its resources is the reason JARs exist. It is the same idea as any archive format, applied to the unit Java actually ships in.

8. The Interactions pane

Worked example

One of the most useful features of DrJava is the Interactions pane at the bottom of the window. It provides the ability to try out code quickly, without having to write a class definition and save/compile/run the program.

> int x = 5;
> int y = 3;
> x > y
true
> x % y
2
lineends with a semicolon?displays
int x = 5;yesnothing
x > ynotrue
x % yno2

Type an expression and press enter.

Why: No class, no main method, no compile step.

Notice the semicolon rule.

Why: If you don't end an expression (or statement) with a semicolon, DrJava automatically displays its value.

Which saves a lot of typing.

Why: This feature saves you from having to type System.out.println every time.

And repeat with the arrows.

Why: You can press the up/down arrows on the keyboard to repeat previous commands and experiment with incremental differences.

Verify: Try -1 % 4 in an interactions pane and confirm Lesson 16's claim that it is −1.

Why: Checking a language rule takes ten seconds instead of five minutes. Most Java environments have something equivalent — modern JDKs ship jshell, which does the same job from the command line — and having one available changes how often you check rather than guess.

9. What does the JVM do?

Prediction

Two tools, two jobs.

javac Hello.java     // produces Hello.class
java Hello           // ?
toolinput
javacsource code
javabyte code

Predict first

What does the JVM do with Hello.class?

  • Interprets the compiled byte code
  • Compiles it into machine code and saves it
  • Checks its style
  • Converts it back into source

Correct: Interprets the compiled byte code

Why: The Java Virtual Machine interprets the compiled byte code. That two-step arrangement — compile once to byte code, interpret it anywhere there is a JVM — is what makes the same .class file run unchanged on Windows, macOS and Linux.

10. Why the book kept this out of the main text

Concept

We avoided putting these details in the main text, because they can be distracting. The decision is worth noticing.

the languagethe tools
changesslowlyconstantly
depends on your OSnoyes
depends on your editornoyes
what you learntransfers everywheretransfers partly

The tools in this appendix have already shifted since it was written — DrJava is no longer widely used, and JUnit has moved on two major versions. The ideas have not: automated tests, style checks, breakpoints and a call stack are as relevant as ever, which is exactly why they were worth separating from the syntax.

11. Confusing the JDK with the JVM

Trap

The trap

Installing a runtime and expecting to compile.

$ javac Hello.java
javac: command not found

# but this works:
$ java -version
java version "17.0.1"
you havecan run programs?can compile?
a JRE — runtime onlyyesno
a JDKyesyes

The confusing part is that java works perfectly. A runtime contains the JVM and not the compiler, so the machine can run somebody else's compiled program and not build your own.

The fix

Install the JDK — it contains everything.

# The Java Development Kit includes:
#   javac    the compiler
#   java     the JVM
#   javadoc  the documentation tool
#   jar      the archive tool
toolin the JDK?job
javacyessource to byte code
javayesruns byte code
javadocyesLesson B's subject

The JDK contains the compiler, Javadoc, and other tools — including the JVM. Installing it is always the right answer for a developer; the runtime-only distribution exists for people who only need to run things.

12. Which tool for which job?

Definition probe

Four pieces of a setup.

Sort into buckets

Sort each description.

JDK
contains the compiler and Javadoc
JVM
interprets byte code
text editor
edits plain text files
IDE
combines editing, compiling and debugging
jdk
The development kit — the compiler, the JVM, Javadoc and the other command-line tools.
jvm
The part that actually runs a compiled program, and the reason the same .class file works on any operating system.
ed
Source code is plain text, so any editor that produces plain text will do.
ide
Everything in one program — convenient, and not required for anything in this book.

13. Why no semicolon?

Prediction

In the Interactions pane.

> int x = 5;
> x > 3
linedisplays
with a semicolonnothing
without one?

Predict first

What does the second line show?

  • true — omitting the semicolon displays the value
  • Nothing
  • A syntax error
  • x > 3

Correct: true — omitting the semicolon displays the value

Why: If you don't end an expression (or statement) with a semicolon, DrJava automatically displays its value. It is a convenience of the interactive pane rather than a Java rule — inside a real program the semicolon is always required.

14. Why is an interactive pane so useful?

Explain it to yourself

You could always write a small main method.

Discussion prompt

Checking -1 % 4 in a main method takes about a minute. In an interactions pane it takes ten seconds. Why does that difference matter more than it sounds?

Hint: How often would you check at each cost?

Answer:

Because a minute is enough to make you guess instead. At ten seconds you check; at a minute you think it's probably fine and move on.

You don't have to create a new class, declare a main method, write arbitrary expressions inside System.out.println statements, save the source file, and get all of your code to compile in advance — five steps removed.

Lowering the cost of a check changes how often it happens, which is the same argument as Lesson 4b's incremental development. A tool that makes the right habit cheap is worth more than one that makes a rare task possible.

15. The command line

Section

Section A.3

16. Four commands to start with

Concept

One of the most powerful and useful skills you can learn is how to use the command-line interface, also called the terminal. The command line is a direct interface to the operating system.

$ cd Desktop
$ ls
Hello.java
$ javac Hello.java
$ ls
Hello.class    Hello.java
$ java Hello
Hello, World!
commanddoes
cdchange the working directory
lslist directory contents
javaccompile Java programs
javarun Java programs

command-line interface — A means of interacting with the computer by issuing commands in the form of successive lines of text.

On Unix systems like Linux and macOS, you can get started with just four commands. It allows you to run programs, manage files and directories, and monitor system resources. Many advanced tools, both for software development and general-purpose computing, are available only at the command line.

17. A file name against a class name

Notation

The two Java commands look symmetrical and take different kinds of argument. That asymmetry catches everyone once.

Annotate

  • javac compiles files, so it wants file names — and can take several at once, separated by spaces.
  • java runs a class, so it wants the class's name. java Hello.class is an error.
  • Running ls after compiling shows the new file: Hello.class, which contains the byte code.
  • The class name must match the file name for a public class, which is why they look so similar and are not the same thing.
  • If you use DrJava, it runs these commands for you behind the scenes and shows the output in the Interactions pane.

Knowing what your IDE is doing for you is worth the ten minutes it takes to do it by hand once. When the IDE goes wrong, the command line is where you find out why.

18. Compiling and running by hand

Worked example

Figure A.3 shows an example in which the Hello.java source file is stored in the Desktop directory.

$ cd Desktop
$ ls
Hello.java
$ javac Hello.java
$ ls
Hello.class    Hello.java
$ java Hello
Hello, World!
stepcommandresult
1cd Desktopthe working directory changes
2lsHello.java
3javac Hello.javano output — success is silent
4lsHello.class appears
5java HelloHello, World!

Change to the right directory.

Why: After changing to that location and listing the files, you can see what is there.

Compile.

Why: We use the javac command to compile Hello.java.

Look at what appeared.

Why: Running ls again, we see that the compiler generated a new file, Hello.class, which contains the byte code.

Run it.

Why: We run the program by using the java command, which displays the output on the following line.

Verify: Notice that step 3 printed nothing at all.

Why: On the command line, silence means success. A compiler that says nothing has found nothing to complain about — which takes some getting used to after an IDE that shows a green tick.

19. What file does javac produce?

Prediction

Compiling Hello.java.

$ javac Hello.java
$ ls
beforeafter
Hello.java?

Predict first

What does ls show now?

  • Hello.class and Hello.java — the byte code alongside the source
  • Only Hello.class
  • Only Hello.java — javac prints to the screen
  • Hello.jar

Correct: Hello.class and Hello.java — the byte code alongside the source

Why: The compiler generated a new file, Hello.class, which contains the byte code. The source is left alone — compiling adds a file rather than replacing one, which is why you can recompile as often as you like.

20. Why it is worth learning

Concept

The appendix makes an unusually strong claim for a textbook, and it is worth taking seriously.

what the command line gives youwhy it matters
tools with no graphical versionmany exist only there
exact repeatabilitya command can be scripted
visibilityyou see what is actually happening
portabilitythe same commands on any Unix machine

Taking time to learn this efficient and elegant way of interacting with the operating system will make you more productive. People who don't use the command line don't know what they're missing. The next section is a concrete example: automated testing, which needs the command line and takes four steps.

21. Passing the wrong thing to java

Trap

The trap

java takes a class name, not a file.

$ java Hello.class
Error: Could not find or load main class Hello.class

$ java Hello.java
# on older JDKs: an error
# on newer ones: compiles and runs in one step - a different feature
argumentwhat java looks for
Helloa class named Hello — correct
Hello.classa class named Hello.class — nonexistent
Hello.javasingle-file source mode, on JDK 11 and later

The error message is honest and confusing: it really is looking for a class called Hello.class, because it took the whole argument as a name. The dot is legal in a class name, so nothing warns you.

The fix

File name to compile, class name to run.

$ javac Hello.java      # file
$ java Hello            # class
commandtakesmay take several?
javacfile namesyes, separated by spaces
javaone class nameno

The javac command requires a filename (or multiple source files separated by spaces), whereas the java command requires a single class name. One compiles many things; the other starts one program.

22. Which command?

Definition probe

Four commands, four jobs.

Sort into buckets

Sort each task.

cd
move to the Desktop folder
ls
see which files are here
javac
turn source into byte code
java
start the program
cd
Change the working directory — everything else operates relative to where you are.
ls
List directory contents, which is how you check that a compile actually produced something.
javac
The compiler. It takes file names and produces .class files.
java
The JVM. It takes a single class name and runs its main method.

23. Compile and run

Fill the middle

One takes a file; one takes a class.

Fill in the blanks

$ javac Hello.java
$ java Hello

Why: javac compiles a source file into byte code and says nothing if it succeeds; java starts the JVM on a named class. The argument forms differ deliberately — one names a file on disk, the other names a class to load.

24. Why do professionals still use the terminal?

Real world

Every IDE has buttons for all of this.

Discussion prompt

Modern development environments compile, run, test and debug with a click. Why is the command line still worth learning?

Hint: What can you write down and repeat?

Answer:

Because a command can be scripted and a click cannot. Build servers, deployment pipelines and test automation are all just sequences of commands — the button is the exception, not the rule.

And many tools have no graphical version at all — Checkstyle in the next section is one, and so is most of what runs on a server you connect to remotely.

It also shows you what the IDE is doing. When a build fails for a reason the IDE cannot explain, running the same command by hand is usually how you find out — which is Lesson 4b's make it visible applied to your tools rather than your code.

25. Command-line testing

Section

Section A.4

26. Stop retyping the same input

Concept

Most, if not all, testing is based on a simple idea: does the program do what we expect it to do? For simple programs, it's not difficult to run them several times and see what happens. But at some point, you will get tired of typing the same test cases over and over.

java Convert < test.in > test.out
fileholds
test.inthe input — as if typed at the keyboard
test.expthe expected output
test.outwhat the program actually produced

redirection operator — A command-line feature that substitutes System.in and/or System.out with a plain text file.

The basic idea is to store the test cases in plain text files and trick Java into thinking they are coming from the keyboard. The program is not modified at all — it still reads with a Scanner and prints with println, and neither notices.

27. The two operators

Notation

One line does two different things, and the direction of each arrow is the whole of it.

Annotate

  • < feeds a file in. The Scanner reads from it exactly as it would read typing.
  • > captures what comes out. Nothing appears on screen — it all goes into the file.
  • The program is unchanged. No test-mode flag, no special build, no code that knows it is being tested.
  • The arrows point the way the data flows, which is the mnemonic worth keeping.
  • > overwrites the file each time, so a re-run always produces a fresh test.out.

Redirection is an operating-system feature, not a Java one. Any program that reads standard input and writes standard output can be tested this way, in any language.

28. A complete test in four steps

Worked example

Here are step-by-step instructions for testing the Convert example from Chapter 3.

# 1. test.in  - the input
193.04

# 2. test.exp - the expected output
193.04 cm = 6 ft, 4 in

# 3. run it
java Convert < test.in > test.out

# 4. compare
diff test.exp test.out
filecreated bycontains
test.inyouthe input
test.expyouwhat you expect
test.outthe programwhat actually happened
diff's outputdiffthe differences, if any

Write the input file.

Why: **Create a plain text file named test.in — in is for input.**

Write the expected output.

Why: **Create a second plain text file named test.exp — exp is for expected.**

Run with both redirections.

Why: The program reads from test.in and writes to test.out.

Compare.

Why: The diff utility summarizes the differences between two files.

Verify: Run diff on two identical files and note that it prints nothing.

Why: If there are no differences, it displays nothing, which in our case is what we want. Silence is the pass condition — the same convention as javac, and worth getting used to.

29. What does `<` do?

Prediction

Two operators, two directions.

java Convert < test.in > test.out
operatorconnects
<?
>System.out to test.out

Predict first

What does the first redirection do?

  • Feeds the contents of test.in into System.in, as if typed
  • Writes the program's output into test.in
  • Compares the two files
  • Compiles test.in

Correct: Feeds the contents of test.in into System.in, as if typed

Why: The first one redirects the contents of test.in to System.in, as if it were entered from the keyboard. The Scanner in the program reads it without knowing anything has changed — which is what makes this work on programs that were never designed to be tested.

30. When the test fails

Concept

If the files are the same, then the program outputted what we expected it to output. If not, then we found a bug, and we can use the output to begin debugging our program.

diff outputmeansnext step
nothingthe test passedmove on
a differenceexpected and actual disagreeinvestigate
usuallythe program is at faultfix the program
sometimesthe expected output is wrongfix the test

Usually, the program is at fault, and diff provides some insight about what is broken. But there's also a chance that we have a correct program and the expected output is wrong. That last possibility is worth remembering: a failing test is evidence that two things disagree, not proof of which one is wrong.

31. Comparing by eye instead of with diff

Trap

The trap

Trailing whitespace and line endings are invisible.

expected:  193.04 cm = 6 ft, 4 in
actual:    193.04 cm = 6 ft, 4 in␣

# looks identical. It is not.
differencevisible to a reader?visible to diff?
a trailing spacenoyes
a missing newline at the endnoyes
a tab instead of spacesusually notyes
two vs three decimal placesyesyes

The first three rows are exactly the differences that waste an afternoon. A person reading two outputs side by side sees what they expect to see, which is why the comparison should be mechanical.

The fix

Let a tool do the comparing.

diff test.exp test.out
toolplatform
diffany Unix-like system
WinMergeWindows
opendiffmacOS, with Xcode
meldLinux

Interpreting the results from diff can be confusing, but fortunately many graphical tools can show the differences between two files. Regardless of what tool you use, the goal is the same. Debug your program until the actual output is identical to the expected output.

32. What does diff print on success?

Prediction

The two files are identical.

diff test.exp test.out
filesoutput
identical?
differenta summary of the differences

Predict first

What appears?

  • Nothing at all
  • OK
  • The contents of both files
  • A count of matching lines

Correct: Nothing at all

Why: If there are no differences, it displays nothing, which in our case is what we want. Silence as success is a Unix convention — the same reason javac prints nothing when a compile works, and it makes tools easy to chain together.

33. Which file is which?

Definition probe

Three files, three roles.

Sort into buckets

Sort each file.

you write it
test.in; test.exp
the program writes it
test.out; the one the program writes
you
The input the program should read, and the output you expect it to produce — both written by hand before the test runs.
prog
Whatever actually came out, captured by the > operator so it can be compared against your expectation.

34. Why store test cases in files?

Explain it to yourself

You could just type them each time.

Discussion prompt

For one test the file is more work than typing. What changes when there are twenty tests, or when you come back next week?

Hint: What can you repeat exactly?

Answer:

A file can be re-run exactly. Typing introduces variation, and the whole value of a test is that the same input produces the same comparison every time.

And twenty of them can be run in a loop. Once a test is a pair of files, running the whole suite is one command rather than twenty minutes of typing.

The tests also outlive your memory of them. Next week you will not remember which inputs were interesting; the files will — which is why they belong beside the source code.

35. Checkstyle and the debugger

Section

Sections A.5-A.6

36. A tool that reads your style

Concept

Checkstyle is a command-line tool that can be used to determine if your source code follows a set of style rules. It also checks for common programming mistakes, such as class and method design problems.

java -jar checkstyle-*-all.jar -c /google_checks.xml *.java

Hello.java:93:5: Missing a Javadoc comment
part of the commandmeans
java -jar checkstyle-*-all.jarrun the JAR
-c /google_checks.xmluse Google's rules, from inside the JAR
*.javaevery Java source file here
the outputfile, line, column, and the problem

wildcard — A command-line feature that allows you to specify a pattern of filenames by using the * character.

**The * characters are wildcards that match whatever version of Checkstyle you have and whatever Java source files are present. The output indicates the file and line number of each problem. /google_checks.xml is inside the JAR file and represents most of Google's style rules.**

37. What a style checker cannot do

Notation

The appendix is careful to bound the claim, and the boundary is the interesting part.

Annotate

  • It can tell you a comment is missing; it cannot tell you a comment is useless. // increment i passes every check.
  • It can enforce naming conventions and not naming judgement. int x2 is perfectly styled and tells you nothing.
  • And it has no opinion about your algorithm — a correctly formatted bubble sort of a million items passes cleanly.
  • Good comments make it easier for experienced developers to identify errors in your code.
  • Good variable names communicate the intent of your program and how the data is organized. And good programs are designed to be efficient and demonstrably correct.

A style checker enforces the part of quality that can be mechanised, which is real and small. The rest is judgement — and knowing which is which stops you mistaking a clean report for good code.

38. Tracing with a debugger

Worked example

A great way to visualize the flow of execution, including how parameters and arguments work, is to use a debugger.

// Most debuggers make it possible to do the following:
//
//   Set a BREAKPOINT, a line where you want the program
//     to pause.
//   STEP THROUGH the code one line at a time and watch
//     what it does.
//   Check the VALUES OF VARIABLES and see when and how
//     they change.
featureanswers the question
breakpointwhat is true when execution reaches here?
steppingwhat happens next?
variable inspectionwhat is this value right now?
the call stackhow did we get here?

Set a breakpoint on a line you care about.

Why: Execution pauses there rather than at the start.

Run, and look at the call stack.

Why: The debugging pane displays the call stack, with the current method on top of the stack.

Be surprised by its size.

Why: You might be surprised to see how many methods were called before the main method!

Step, and watch the variables.

Why: When the program is paused, you can examine (or even change) the value of any variable.

Verify: Recognise the call stack as Lesson 4a's stack diagram, drawn by the machine.

Why: Tracing allows you to follow the flow of execution and see how data passes from one method to another. You might expect the code do one thing, but then the debugger shows it doing something else. At that moment, you gain insight about what may be wrong with the code.

39. What is a breakpoint?

Prediction

You set one before running.

// Ctrl+B toggles a breakpoint on the current line
without onewith one
the program runs to completion?

Predict first

What does it do?

  • Pauses the running program when execution reaches that line
  • Stops the program permanently
  • Prints the line when it runs
  • Skips the line

Correct: Pauses the running program when execution reaches that line

Why: A breakpoint is a line of code at which the debugger will pause a running program. Pausing rather than stopping is the point — the program's whole state is still there to inspect, and you can resume or step from where it stopped.

40. Print statements against a debugger

Concept

Lesson 15b traced a binary search with System.out.println. A debugger does the same job differently.

print statementsdebugger
setupedit the codenone — set a breakpoint
what you seeonly what you printedevery variable
changing your mindedit and recompilelook at something else
cleaning updelete the printsnothing to delete
works everywhereyesneeds tool support

Neither replaces the other. A print statement survives into a log and works in places a debugger cannot reach; a debugger shows you everything without deciding in advance what to look at. Knowing both means picking the one that fits — which the appendix says explicitly about the command line too.

41. Editing code while it is paused

Trap

The trap

Changing a program mid-run.

// paused at a breakpoint, you add three lines above it
// and delete one below

// the debugger's line numbers no longer match the file
what you seewhat is running
the edited sourcethe compiled version from before
breakpoint on line 42a different statement now
your conclusionsabout code that is not executing

You can edit your code while debugging it, but we don't recommend it. If you add or delete multiple lines of code while the program is paused, the results can be confusing.

The fix

Observe now; edit after.

// 1. run to the breakpoint
// 2. inspect variables, step, work out what is wrong
// 3. STOP the program
// 4. then edit and recompile
phasewhat you are doing
pausedgathering evidence
stoppedchanging the code
running againchecking the change

Observation and modification are separate activities, and mixing them makes both unreliable — the same discipline as Lesson 16's rule about refactoring and changing behaviour in one edit.

42. Can a style checker judge it?

Definition probe

Mechanical rules against judgement.

Sort into buckets

Sort each aspect of code quality.

Checkstyle can check it
indentation and brace placement; whether a method has a Javadoc comment
only a person can judge it
whether a comment is useful; whether the algorithm is efficient
yes
It is a mechanical property of the text, which a tool can verify exactly and tirelessly.
no
It requires understanding what the code means — the quality of comments, the meaning of names, and the structure of algorithms are explicitly beyond automatic checkers.

43. Match the tool to its job

Matching

Four tools from this appendix.

Match the pairs

  • a. javac
  • b. diff
  • c. Checkstyle
  • d. a debugger
  • r1. turns source into byte code
  • r2. compares expected output with actual
  • r3. reports style rule violations
  • r4. pauses execution and shows variables

Why: Four tools, four different kinds of feedback: does it compile, does it produce the right answer, is it written conventionally, and what is it actually doing. Each catches problems the others cannot.

44. What does the call stack tell you?

Socratic

You might be surprised to see how many methods were called before the main method!

Discussion prompt

Why are there methods below main on the stack, and what does that reveal about how a Java program starts?

Hint: Who calls main?

Answer:

Because something called main. The JVM has its own startup code — class loading, thread creation, argument handling — and all of it is on the stack beneath your program.

It is a reminder that main is an ordinary method that happens to be where your part begins. Lesson 4a's stack diagrams stopped at main because that was as far as your code went, not as far as the stack did.

**The call stack answers how did we get here, which no print statement easily can.** For a method called from twenty places, that is often the whole question.

45. Testing with JUnit

Section

Section A.7

46. How beginners test, and why it does not scale

Concept

When beginners start writing methods, they usually test them by invoking them from main and checking the results by hand.

public static void main(String[] args) {
    if (fibonacci(1) != 1) {
        System.err.println("fibonacci(1) is incorrect");
    }
    if (fibonacci(2) != 1) {
        System.err.println("fibonacci(2) is incorrect");
    }
    if (fibonacci(3) != 2) {
        System.err.println("fibonacci(3) is incorrect");
    }
}
problemdetail
lengththree lines per case
scalingtwenty cases is sixty lines
the messagesays which case, not what went wrong
running itmixed in with the real main

This test code is self-explanatory, but it's longer than it needs to be, and it doesn't scale very well. In addition, the error messages provide limited information. Note it does use System.err rather than System.out — the error stream, which is at least the right channel.

47. The naming convention

Notation

JUnit relies on names matching, and the rule is worth learning because tools everywhere use it.

Annotate

  • Class Series gets a test class SeriesTest — the name says what it tests.
  • Method fibonacci gets a test method testFibonacci — so a failure names the method under test.
  • The convention is what lets tools find your tests without configuration.
  • Many development environments can generate test classes and test methods automatically — in DrJava, New JUnit Test Case from the File menu.
  • Convention over configuration is a general idea: agree on names, and no wiring is needed.

The naming is not cosmetic. A test suite of two hundred methods is navigable only because each name says exactly what it covers — which is the same argument as good variable names, at a larger scale.

48. A JUnit test class

Worked example

JUnit is a common testing tool for Java programs. To use it, you have to create a test class that contains test methods.

import junit.framework.TestCase;

public class SeriesTest extends TestCase {

    public void testFibonacci() {
        assertEquals(1, Series.fibonacci(1));
        assertEquals(1, Series.fibonacci(2));
        assertEquals(2, Series.fibonacci(3));
    }
}
partmeans
import junit.framework.TestCasethe class comes from the JUnit library
extends TestCaseinheritance — Lesson 14a
testFibonaccione test method, named by convention
assertEquals(1, ...)expected first, actual second

Extend TestCase.

Why: This example uses the keyword extends, which indicates that the new class, SeriesTest, is based on an existing class, TestCase.

Which is imported.

Why: The TestCase class is imported from the package junit.framework.

Write one method per method under test.

Why: testFibonacci for fibonacci.

Assert what you expect.

Why: assertEquals is provided by the TestCase class. It takes two arguments and checks whether they are equal.

Verify: Compare the three assertEquals lines with the nine lines of hand-written if statements.

Why: Using assertEquals is more concise than writing your own if statements and System.err messages — and when one fails, it displays a detailed error message naming both values rather than just saying which case broke. Note this is JUnit 3, which the book flags: superseded, but the default version supported by DrJava.

49. Which argument comes first?

Prediction

assertEquals takes two.

assertEquals(1, Series.fibonacci(1));
positionrole
first?
secondthe value being checked

Predict first

What is the first argument?

  • The expected value — what you consider correct
  • The actual value your code produced
  • The name of the test
  • The tolerance

Correct: The expected value — what you consider correct

Why: The first argument is the expected value, which we consider correct, and the second argument is the actual value we want to check. Reversing them does not change whether the test passes — it changes the failure message, which will then describe the two values backwards.

50. Expected first, actual second

Concept

The first argument is the expected value, which we consider correct, and the second argument is the actual value we want to check. If they are not equal, the test fails.

assertEquals(1, Series.fibonacci(1));
//           ^  ^
//           |  the ACTUAL value - what your code produced
//           the EXPECTED value - what you say is correct

// JUnit provides additional assert methods:
assertNull(x);
assertSame(a, b);
assertTrue(condition);
methodchecks
assertEquals(e, a)the two are equal
assertNull(x)x is null
assertSame(a, b)the same object — Lesson 11b's ==
assertTrue(c)the condition holds

Getting the order backwards does not break the test — it still passes or fails correctly. It breaks the message: a failure will report expected 5 but was 3 with the two the wrong way round, which is confusing exactly when you are least able to spare the confusion.

51. Testing only the cases you know work

Trap

The trap

Three assertions, all of them the easy path.

public void testFibonacci() {
    assertEquals(1, Series.fibonacci(1));
    assertEquals(1, Series.fibonacci(2));
    assertEquals(2, Series.fibonacci(3));
}
untestedwhy it matters
fibonacci(0)the base case boundary
a large inputwhere overflow or slowness appears
a negative inputwhat should it even do?
the recursion depthLesson 8a's StackOverflowError

The three assertions are a fine start and they all exercise the same shallow path. A test suite that only covers what you were already confident about tells you very little.

The fix

Test the edges, and decide what the odd cases should do.

public void testFibonacci() {
    assertEquals(1, Series.fibonacci(1));   // base case
    assertEquals(1, Series.fibonacci(2));   // base case
    assertEquals(2, Series.fibonacci(3));   // first recursive case
    assertEquals(55, Series.fibonacci(10)); // deeper
    // and decide: what should fibonacci(0) or fibonacci(-1) do?
}
kind of casewhy include it
the base caseswhere recursion stops
the first recursive casewhere it starts
something deeperwhere accumulated errors appear
invalid inputforces you to decide the behaviour

The last row is the valuable one. Writing a test for fibonacci(-1) forces you to decide what it should do — throw, return zero, or be documented as undefined — which is a design question the tests surfaced. Lesson 17a's argument validation is the same idea arriving from the other direction.

52. What should the test class be called?

Prediction

The method under test lives in Series.

public class Series {
    public static int fibonacci(int n) { ... }
}
classtest class
Series?

Predict first

By convention, what is the test class named?

  • SeriesTest
  • TestSeries
  • SeriesTests
  • FibonacciTest

Correct: SeriesTest

Why: If the name of your class is Something, the name of the test class should be SomethingTest — the suffix goes at the end. The methods follow the other pattern: a method someMethod is tested by testSomeMethod, with the prefix at the front.

53. Write a JUnit test

Fill the middle

Expected first, actual second.

Fill in the blanks

public class SeriesTest extends TestCase assertEquals}(2, Series.fibonacci(3));
}
}

Why: In JUnit 3 a test class extends TestCase, which is where assertEquals and the other assert methods come from — ordinary inheritance from Lesson 14a. The expected value comes first so that a failure message reads correctly.

54. Why write tests at all?

Real world

You can always run the program and look.

Discussion prompt

Running Convert by hand and reading the output works. What do automated tests give you that manual checking does not?

Hint: What happens when you change something later?

Answer:

They check everything, every time, for free. Manual testing checks the thing you just changed; an automated suite checks the twenty things you did not think about.

Which is what makes changing code safe. Refactoring — Lesson 16 — is defined as restructuring without changing behaviour, and a test suite is how you know the behaviour did not change.

And a failing test is a precise report. testFibonacci: expected 2 but was 3 names the method, the input and both values — which is where debugging starts rather than where it stalls.

55. Three ways to find out what your code does

Comparison

Fill the blanks.

Comparison matrix

print statementsdebuggerunit tests
when you use itwhile writingwhen something is wrongevery time, automatically
requires editing the codeyesnono — a separate class
shows youwhat you chose to printevery variablepass or fail, with both values
survives into the futureno — you delete themnoyes

The last row is why tests are worth the extra effort. A print statement helps you today; a test keeps helping — which is what makes refactoring safe six months later.

56. The pattern to carry away

Pattern

Make the check automatic, so it happens whether or not you remember.

# a test that runs without you typing anything
java Convert < test.in > test.out
diff test.exp test.out

# a test that names its own failure
public void testFibonacci() {
    assertEquals(1, Series.fibonacci(1));
    assertEquals(2, Series.fibonacci(3));
}

# a check you cannot forget to run
java -jar checkstyle-*-all.jar -c /google_checks.xml *.java
toolanswerssilence means
javacdoes it compile?success
diffis the output right?success
Checkstyleis it written conventionally?success
JUnitdo the methods behave?a green bar

57. Check: the two commands

Check

Work it out before you click.

$ javac Hello.java
$ java Hello
commandargument
javacHello.java
javaHello

Check your understanding

Why does one argument have .java and the other does not?

  • A. javac takes a file name; java takes a class name (correct)
  • B. java always drops the extension automatically
  • C. The .java is optional in both
  • D. java is looking for Hello.class, so it uses no extension

Answer: A

Why: The javac command requires a filename (or multiple source files separated by spaces), whereas the java command requires a single class name. java Hello.class fails because it looks for a class actually named Hello.class — the dot is legal in a name, so nothing warns you.

Why B tempts people
Nothing is dropped; the argument is a class name and always was.
Why C tempts people
javac Hello fails — it needs a file to read.
Why D tempts people
It does load Hello.class, but the argument names the class, not the file.

58. Check: redirection

Check

Work it out before you click.

java Convert < test.in > test.out
operatorstream
<System.in
>System.out

Check your understanding

What does this command do?

  • A. Runs Convert reading test.in as if typed, and writes its output into test.out (correct)
  • B. Compares test.in with test.out
  • C. Compiles test.in and runs it
  • D. Reads test.out and writes test.in

Answer: A

Why: The first one redirects the contents of test.in to System.in, as if it were entered from the keyboard. The second one redirects the contents of System.out to a new file test.out. The program itself is unchanged — which is why this works on any program that reads standard input, in any language.

Why B tempts people
That is what diff does, on the next line.
Why C tempts people
test.in holds input data, not source code.
Why D tempts people
The arrows point the way the data flows, and both point away from test.in.

59. Check: assertEquals

Check

Work it out before you click.

assertEquals(1, Series.fibonacci(1));
argumentrole
1?
Series.fibonacci(1)?

Check your understanding

What does each argument mean?

  • A. 1 is the expected value; the call produces the actual value (correct)
  • B. 1 is the actual value; the call is the expectation
  • C. 1 is the number of times to run the test
  • D. The order does not matter at all

Answer: A

Why: The first argument is the expected value, which we consider correct, and the second argument is the actual value we want to check. The order does not affect whether the test passes — but it does affect the failure message, which will otherwise describe the two values backwards at exactly the wrong moment.

Why B tempts people
This reverses the convention; the literal is what you assert to be correct.
Why C tempts people
assertEquals compares two values; it does not repeat anything.
Why D tempts people
It matters for the message, which is most of what a failing test is for.

60. Tools change; the ideas do not

Real world

DrJava is barely used now and JUnit has moved on two major versions since this appendix was written.

Discussion prompt

Which parts of this appendix have dated, and which are as true as ever?

Hint: Separate the tool from the technique.

Answer:

The specific tools have dated. Most people now use IntelliJ IDEA, Eclipse or VS Code; JUnit 5 uses annotations rather than extends TestCase; and modern JDKs ship jshell in place of an Interactions pane.

Everything else is unchanged. The JDK still contains javac, redirection still works exactly as described, breakpoints and call stacks are identical in every debugger, and assertEquals(expected, actual) survived the version change untouched.

Which is why the appendix separates them from the main text. Learn the technique and the tool is a detail; learn only the tool and you start over each time one is replaced.

61. How sure are you?

Commit first

Commit to an answer and to your confidence.

Predict first

What can a style checker like Checkstyle NOT evaluate?

  • The quality of your comments, the meaning of your variable names, and the structure of your algorithms
  • Indentation and brace placement
  • Whether a method has a Javadoc comment
  • Line length

Correct: The quality of your comments, the meaning of your variable names, and the structure of your algorithms

Why: There are limits to what automatic style checkers can do. In particular, they can't evaluate the quality of your comments, the meaning of your variable names, or the structure of your algorithms. A checker can confirm a Javadoc comment exists and has no opinion on whether it says anything useful; it can enforce a naming convention and cannot tell you that x2 is a bad name. The other three options are exactly the mechanical properties it does check — which is real value, and a small part of what makes code good.

62. Explain it to someone else

Explain it

Two minutes, out loud.

Discussion prompt

A classmate is retyping the same three test inputs every time they change their program. Show them the faster way and explain each part of the command.

Hint: Two files, two arrows, one comparison.

Answer:

Put the input in a file called test.in and what you expect in test.exp. Then run java Convert < test.in > test.out.

The < feeds test.in into System.in, so your Scanner reads it as if you had typed it. The > captures everything the program prints into test.out. Your program does not change at all.

Then diff test.exp test.out. If it prints nothing, the test passed. And now re-running the test is one command instead of a minute of typing, which means you will actually do it after every change.

63. Exit ticket

Exit ticket

One question before you close the deck.

Predict first

What is the difference between the JDK and the JVM?

  • The JDK is the development kit containing the compiler and other tools; the JVM interprets compiled byte code and is part of it
  • The JDK runs programs and the JVM compiles them
  • They are two names for the same thing
  • The JVM is an IDE

Correct: The JDK is the development kit containing the compiler and other tools; the JVM interprets compiled byte code and is part of it

Why: JDK: the Java Development Kit, which contains the compiler, Javadoc, and other tools. JVM: the Java Virtual Machine, which interprets the compiled byte code. The practical consequence is the trap: a runtime-only installation gives you java and not javac, so you can run compiled programs and not build your own — and the symptom is javac: command not found while java -version works perfectly. Installing the JDK is always the right answer for someone writing code.

64. Draw the whole lesson

Connect it up

One page, from memory.

Draw it

Draw the pipeline from Hello.java through javac to Hello.class through java to output, labelling which pieces live in the JDK. Beneath it write the four-step command-line test — the two files you write, the command with both redirection arrows, and the diff — annotating what each arrow connects. Then write a three-line JUnit test method, marking which argument of assertEquals is expected and which is actual. Finish with the three things a style checker cannot evaluate.

65. Recap

Recap

Eight sections of machinery, deliberately kept out of the main text so it would not distract from the language.

if you remember one thingit is this
about the toolchainknow what your IDE is doing for you
about testingmake the check automatic or it will not happen
about tools generallylearn the technique; the tool is a detail

Sources

  1. Downey & Mayfield, Think Java, 2nd edition (Green Tea Press / O'Reilly, 2020) — Think Java 2e, Chapter A (Tools), Sections A.1-A.8, pp. 297-307
  2. The Java Tutorials — The javac and java Commands
  3. Think Java 2e — free online edition and source code

Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.

Book on Wyzant · Text (657) 465-8108