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
Title
Think Java 2e · Chapter A · Tools
Sections A.1-A.8 · pp. 297-307
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.
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.
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.
Section
Sections A.1-A.2
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.
| piece | what it is | needed? |
|---|---|---|
| JDK | the compiler, the JVM, and tools like Javadoc | yes, to compile locally |
| JVM | interprets the compiled byte code | yes — it is inside the JDK |
| text editor | edits plain text files | yes, in some form |
| IDE | editing, compiling and debugging in one program | no — 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.
Notation
Two of this appendix's tools ship as JAR files, and the vocabulary entry explains why that is convenient.
Annotate
.class files inside.java -jar runs one, using a main class recorded inside the archive./google_checks.xml is inside the JAR file, which is why the path begins with a slash.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.
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| line | ends with a semicolon? | displays |
|---|---|---|
| int x = 5; | yes | nothing |
| x > y | no | true |
| x % y | no | 2 |
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.
Prediction
Two tools, two jobs.
javac Hello.java // produces Hello.class
java Hello // ?| tool | input |
|---|---|
| javac | source code |
| java | byte code |
Predict first
What does the JVM do with Hello.class?
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.
Concept
We avoided putting these details in the main text, because they can be distracting. The decision is worth noticing.
| the language | the tools | |
|---|---|---|
| changes | slowly | constantly |
| depends on your OS | no | yes |
| depends on your editor | no | yes |
| what you learn | transfers everywhere | transfers 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.
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 have | can run programs? | can compile? |
|---|---|---|
| a JRE — runtime only | yes | no |
| a JDK | yes | yes |
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.
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| tool | in the JDK? | job |
|---|---|---|
| javac | yes | source to byte code |
| java | yes | runs byte code |
| javadoc | yes | Lesson 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.
Definition probe
Four pieces of a setup.
Sort into buckets
Sort each description.
Prediction
In the Interactions pane.
> int x = 5;
> x > 3| line | displays |
|---|---|
| with a semicolon | nothing |
| without one | ? |
Predict first
What does the second line show?
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.
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.
Section
Section A.3
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!| command | does |
|---|---|
| cd | change the working directory |
| ls | list directory contents |
| javac | compile Java programs |
| java | run 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.
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.ls after compiling shows the new file: Hello.class, which contains the byte code.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.
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!| step | command | result |
|---|---|---|
| 1 | cd Desktop | the working directory changes |
| 2 | ls | Hello.java |
| 3 | javac Hello.java | no output — success is silent |
| 4 | ls | Hello.class appears |
| 5 | java Hello | Hello, 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.
Prediction
Compiling Hello.java.
$ javac Hello.java
$ ls| before | after |
|---|---|
| Hello.java | ? |
Predict first
What does ls show now?
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.
Concept
The appendix makes an unusually strong claim for a textbook, and it is worth taking seriously.
| what the command line gives you | why it matters |
|---|---|
| tools with no graphical version | many exist only there |
| exact repeatability | a command can be scripted |
| visibility | you see what is actually happening |
| portability | the 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.
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| argument | what java looks for |
|---|---|
| Hello | a class named Hello — correct |
| Hello.class | a class named Hello.class — nonexistent |
| Hello.java | single-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.
File name to compile, class name to run.
$ javac Hello.java # file
$ java Hello # class| command | takes | may take several? |
|---|---|---|
| javac | file names | yes, separated by spaces |
| java | one class name | no |
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.
Definition probe
Four commands, four jobs.
Sort into buckets
Sort each task.
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.
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.
Section
Section A.4
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| file | holds |
|---|---|
| test.in | the input — as if typed at the keyboard |
| test.exp | the expected output |
| test.out | what 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.
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.> 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.
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| file | created by | contains |
|---|---|---|
| test.in | you | the input |
| test.exp | you | what you expect |
| test.out | the program | what actually happened |
| diff's output | diff | the 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.
Prediction
Two operators, two directions.
java Convert < test.in > test.out| operator | connects |
|---|---|
| < | ? |
| > | System.out to test.out |
Predict first
What does the first redirection do?
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.
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 output | means | next step |
|---|---|---|
| nothing | the test passed | move on |
| a difference | expected and actual disagree | investigate |
| usually | the program is at fault | fix the program |
| sometimes | the expected output is wrong | fix 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.
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.| difference | visible to a reader? | visible to diff? |
|---|---|---|
| a trailing space | no | yes |
| a missing newline at the end | no | yes |
| a tab instead of spaces | usually not | yes |
| two vs three decimal places | yes | yes |
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.
Let a tool do the comparing.
diff test.exp test.out| tool | platform |
|---|---|
| diff | any Unix-like system |
| WinMerge | Windows |
| opendiff | macOS, with Xcode |
| meld | Linux |
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.
Prediction
The two files are identical.
diff test.exp test.out| files | output |
|---|---|
| identical | ? |
| different | a summary of the differences |
Predict first
What appears?
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.
Definition probe
Three files, three roles.
Sort into buckets
Sort each file.
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.
Section
Sections A.5-A.6
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 command | means |
|---|---|
| java -jar checkstyle-*-all.jar | run the JAR |
| -c /google_checks.xml | use Google's rules, from inside the JAR |
| *.java | every Java source file here |
| the output | file, 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.**
Notation
The appendix is careful to bound the claim, and the boundary is the interesting part.
Annotate
// increment i passes every check.int x2 is perfectly styled and tells you nothing.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.
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.| feature | answers the question |
|---|---|
| breakpoint | what is true when execution reaches here? |
| stepping | what happens next? |
| variable inspection | what is this value right now? |
| the call stack | how 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.
Prediction
You set one before running.
// Ctrl+B toggles a breakpoint on the current line| without one | with one |
|---|---|
| the program runs to completion | ? |
Predict first
What does it do?
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.
Concept
Lesson 15b traced a binary search with System.out.println. A debugger does the same job differently.
| print statements | debugger | |
|---|---|---|
| setup | edit the code | none — set a breakpoint |
| what you see | only what you printed | every variable |
| changing your mind | edit and recompile | look at something else |
| cleaning up | delete the prints | nothing to delete |
| works everywhere | yes | needs 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.
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 see | what is running |
|---|---|
| the edited source | the compiled version from before |
| breakpoint on line 42 | a different statement now |
| your conclusions | about 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.
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| phase | what you are doing |
|---|---|
| paused | gathering evidence |
| stopped | changing the code |
| running again | checking 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.
Definition probe
Mechanical rules against judgement.
Sort into buckets
Sort each aspect of code quality.
Matching
Four tools from this appendix.
Match the pairs
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.
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.
Section
Section A.7
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");
}
}| problem | detail |
|---|---|
| length | three lines per case |
| scaling | twenty cases is sixty lines |
| the message | says which case, not what went wrong |
| running it | mixed 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.
Notation
JUnit relies on names matching, and the rule is worth learning because tools everywhere use it.
Annotate
Series gets a test class SeriesTest — the name says what it tests.fibonacci gets a test method testFibonacci — so a failure names the method under test.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.
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));
}
}| part | means |
|---|---|
| import junit.framework.TestCase | the class comes from the JUnit library |
| extends TestCase | inheritance — Lesson 14a |
| testFibonacci | one 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.
Prediction
assertEquals takes two.
assertEquals(1, Series.fibonacci(1));| position | role |
|---|---|
| first | ? |
| second | the value being checked |
Predict first
What is the first argument?
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.
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);| method | checks |
|---|---|
| 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.
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));
}| untested | why it matters |
|---|---|
| fibonacci(0) | the base case boundary |
| a large input | where overflow or slowness appears |
| a negative input | what should it even do? |
| the recursion depth | Lesson 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.
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 case | why include it |
|---|---|
| the base cases | where recursion stops |
| the first recursive case | where it starts |
| something deeper | where accumulated errors appear |
| invalid input | forces 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.
Prediction
The method under test lives in Series.
public class Series {
public static int fibonacci(int n) { ... }
}| class | test class |
|---|---|
| Series | ? |
Predict first
By convention, what is the test class named?
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.
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.
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.
Comparison
Fill the blanks.
Comparison matrix
| print statements | debugger | unit tests | |
|---|---|---|---|
| when you use it | while writing | when something is wrong | every time, automatically |
| requires editing the code | yes | no | no — a separate class |
| shows you | what you chose to print | every variable | pass or fail, with both values |
| survives into the future | no — you delete them | no | yes |
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.
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| tool | answers | silence means |
|---|---|---|
| javac | does it compile? | success |
| diff | is the output right? | success |
| Checkstyle | is it written conventionally? | success |
| JUnit | do the methods behave? | a green bar |
< feeds a file into System.in; > captures System.out.Check
Work it out before you click.
$ javac Hello.java
$ java Hello| command | argument |
|---|---|
| javac | Hello.java |
| java | Hello |
Check your understanding
Why does one argument have .java and the other does not?
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.
javac Hello fails — it needs a file to read.Check
Work it out before you click.
java Convert < test.in > test.out| operator | stream |
|---|---|
| < | System.in |
| > | System.out |
Check your understanding
What does this command do?
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.
diff does, on the next line.Check
Work it out before you click.
assertEquals(1, Series.fibonacci(1));| argument | role |
|---|---|
| 1 | ? |
| Series.fibonacci(1) | ? |
Check your understanding
What does each argument mean?
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.
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.
Commit first
Commit to an answer and to your confidence.
Predict first
What can a style checker like Checkstyle NOT evaluate?
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.
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.
Exit ticket
One question before you close the deck.
Predict first
What is the difference between the JDK and the JVM?
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.
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.
Recap
Eight sections of machinery, deliberately kept out of the main text so it would not distract from the language.
| if you remember one thing | it is this |
|---|---|
| about the toolchain | know what your IDE is doing for you |
| about testing | make the check automatic or it will not happen |
| about tools generally | learn the technique; the tool is a detail |
< redirects a file into System.in; > captures System.out into a file.Want this taught 1-on-1? Alexander tutors Java — $55/session, free consultation.