Share
Explore
Learning Outcomes:

Understanding JUnit
Installing JUnit
Writing your first tests
Running tests


All code needs to be tested. During development, the first thing we do is run our own programmer’s acceptance test. We code, compile, and run. When we run, we test. The test may just consist of clicking a button to see whether it brings up the expected menu or looking at a result to compare it with the expected value. Nevertheless, every day, we code, we compile, we run, and we test.

When we test, we often find issues, especially during early runs. So we code, compile, run, and test again.

Most of us quickly develop a pattern for our informal tests: add a record, view a record, edit a record, and delete a record. Running a little test suite like this by hand is easy enough to do, so we do it--over and over again.

If you’re already test-infected,1 this book is also for you. We cover the basics in part 1 and move on to tough, real-life problems in parts 2-5.

1.1 Proving that a program works
Some developers feel that automated tests are essential parts of the development process: you cannot prove that a component works until it passes a comprehensive series of tests.

2 developers felt that this type of unit testing was so important that it deserved its own framework. In 1997, Erich Gamma and Kent Beck created a simple but effective unit testing framework for Java called JUnit: they were on a long plane trip, and it gave them something interesting to do. Erich wanted Kent to learn Java, and Erich was interested in knowing more about the SUnit testing framework that Kent created earlier for Smalltalk, and the flight gave them time to do both.

DEFINITION Framework--A semicomplete application that provides a reusable common structure to share among applications. Developers incorporate the framework into their own applications and extend it to meet their specific needs. Frameworks differ from toolkits by providing a coherent structure rather than a simple set of utility classes. A framework defines a skeleton, and the application defines its own features to fill out the skeleton. The developer code is called appropriately by the framework. Developers can invest less in thinking about whether a design is good and focus more on implementing domain-specific functions.

If you recognize the names Erich Gamma and Kent Beck, that’s for a good reason. Gamma is one of the Gang of Four who gave us the now-classic Design Patterns book.3 Beck is equally well known for his groundbreaking work in the software discipline known as Extreme Programming (www.extremeprogramming.org).

JUnit quickly became the de facto standard framework for developing unit tests in Java. Today, JUnit (https://junit.org) is open source software hosted on GitHub, with an Eclipse Public License.

The underlying testing model, known as xUnit, is on its way to becoming the standard framework for any language. xUnit frameworks are available for ASP, C++, C#, Eiffel, Delphi, Perl, PHP, Python, Rebol, Smalltalk, and Visual Basic--to name just a few.

The JUnit team didn’t invent software testing or even unit tests. Originally, the term unit test described a test that examined the behavior of a single unit of work: a class or a method. Over time, the use of the term unit test broadened. The Institute of Electrical and Electronics Engineers (IEEE), for example, has defined unit testing as “testing of individual hardware or software units or groups of related units” (emphasis added).4

The term unit test describes a test that examines a single unit in isolation from other units. We focus on the type of small, incremental test that programmers apply to their own code. Sometimes, these tests are called programmer tests to differentiate them from quality-assurance or customer tests (http://c2.com/cgi/wiki?ProgrammerTest).

Generic description of a typical unit test: “Confirms that the method accepts the expected range of input [DOMAIN] and that the method returns the expected value for each input. {RANGE}” This description asks us to test the behavior of a method through its interface. If we give it value x, will it return value y? If we give it value z instead, will it throw the proper exception?

DEFINITION Unit test--A test that examines the behavior of a distinct unit of work. A unit of work is a task that is not directly dependent on the completion of any other task. Within a Java application, the distinct unit of work is often, but not always, a single method. In contrast, integration tests and acceptance tests examine how various components interact.

Unit tests often focus on testing whether a method is following the terms of its API contract. Like a written contract between people who agree to exchange certain goods or services under specific conditions, an API contract is a formal agreement made by the signature of a method. A method requires its callers to provide specific object references or primitive values and returns an object reference or primitive value. If the method cannot fulfill the contract, the test should throw an exception, and we say that the method has broken its contract.

DEFINITION API contract--A view of an application programming interface (API) as a formal agreement between the caller and the callee. Often, unit tests help define the API contract by demonstrating the expected behavior. The notion of an API contract arises from the practice of Design by Contract, popularized by the Eiffel programming language (http://archive.eiffel.com/doc/manuals/technology/contract).

LEARING WORKFLOW:

You will walk through creating a unit test from scratch for a simple class.
Start by writing a test and its minimal runtime framework so you can see how things used to be done.
Factor JUnit into your Code.

Start with a Calculator class that adds two numbers. Our calculator, shown in the following listing, provides an API to clients and does not contain a user interface. To test its functionality, we’ll first create our own pure Java tests and later move to JUnit 5.

Listing 1.1 The Calculator class to be tested

public class Calculator {
public double add(double number1, double number2) {
return number1 + number2;
}
}
The compiler can tell you that the code compiles: This is Static (or Structural) Testing

Dynamic testing validates correct operation at runtime.

A core principle of unit testing is, “Any program feature without an automated test simply doesn’t exist.”

Methods ARE program Features.

The add method represents a core feature of the calculator. You have some code that allegedly implements the feature. What is missing is an automated test that proves the implementation works.

Isn’t the add method too simple to break?

The current implementation of the add method is too simple to break with usual, everyday calculations. If add were a minor utility method, you might not test it directly. In that case, if add did fail, tests of the methods that used add would fail. The add method would be tested indirectly, but tested nonetheless. In the context of the calculator program, add is not just a method, but also a program feature. To have confidence in the program, most developers would expect there to be an automated test for the add feature, no matter how simple the implementation appears to be. In some cases, you can prove program features through automatic functional tests or automatic acceptance tests.

How to implement your Tests?

The test program can pass known values to the method and see whether the result matches expectations. You can also run the program again later to be sure the method continues to work as the application grows. So what is the simplest possible test program you could write?

Listing 1.2 A simple test calculator program

public class CalculatorTest {
public static void main(String[] args) {
Calculator calculator = new Calculator();
double result = calculator.add(10, 50);
if (result != 60) {
System.out.println("Bad result: " + result);
}
}
}
CalculatorTest is simple indeed: it creates an instance of Calculator, passes two numbers to it, and checks the result. If the result does not meet your expectations, you print a message on standard output.

If you compile and run this program now, the test quietly passes, and all seems to be well. But what happens if you change the code so that it fails? You have to watch the screen carefully for the error message. You may not have to supply the input, but you are still testing your own ability to monitor the program’s output. You want to test the code, not yourself!

The conventional way to signal error conditions in Java is to throw an exception. Let’s throw an exception to indicate a test failure.

Meanwhile, you may also want to run tests for other Calculator methods that you have not written yet, such as subtract or multiply. Moving to a modular design will make catching and handling exceptions easier; it will also be easier to extend the test program later. The next listing shows a slightly better CalculatorTest program.

Listing 1.3 A (slightly) better test calculator program

public class CalculatorTest {
private int nbErrors = 0;
public void testAdd() { ①
Calculator calculator = new Calculator(); ①
double result = calculator.add(10, 50); ①
if (result != 60) { ①
throw new IllegalStateException("Bad result: " + result); ①
} ①
} ①
public static void main(String[] args) {
CalculatorTest test = new CalculatorTest();
try { ②
test.testAdd(); ②
} ②
catch (Throwable e) { ②
test.nbErrors++; ②
e.printStackTrace(); ②
} ②
if (test.nbErrors > 0) { ②
throw new IllegalStateException("There were " + test.nbErrors
+ " error(s)");
}
}
}
At ①, you move the test into its own testAdd method. Now it’s easier to focus on what the test does. You can also add more methods with more unit tests later without making the main method harder to maintain. At ②, you change the main method to print a stack trace when an error occurs; then, if there are any errors, you end by throwing a summary exception.

Now that you have looked at a simple application and its tests, you can see that even this small class and its tests can benefit from the little bit of skeleton code you created to run and manage test results. But as an application gets more complicated and the tests become more involved, continuing to build and maintain a custom testing framework becomes a burden.

Look at the general case for a unit testing framework.

Understanding unit testing frameworks

Each unit test should run independently of all other unit tests.

The framework should detect and report errors test by test.

It should be easy to define which unit tests will run.

The “slightly better” test program comes close to following these rules but still falls short. For each unit test to be truly independent, for example, each should run in a different class instance.

Adding unit tests
You can add new unit tests by adding a new method and then adding a corresponding try/catch block to main. This is a step up but still short of what you would want in a real unit test suite. Experience tells us that large try-catch blocks cause maintenance problems. You could easily leave out a unit test and never know it!

It would be nice if you could just add new test methods and continue working, but if you did, how would the program know which methods to run? Well, you could have a simple registration procedure. A registration method would at least inventory which tests are running.

Another approach would be to use Java’s reflection capabilities. A program could look at itself and decide to run whatever methods follow a certain naming convention, such as those that begin with test.

Making it easy to add tests (the third rule in the earlier list) sounds like another good rule for a unit testing framework. The support code that realizes this rule (via registration or reflection) would not be trivial, but it would be worthwhile. You’d have to do a lot of work up front, but that effort would pay off each time you add a new test.

Fortunately, the JUnit team has saved you the trouble. The JUnit framework already supports discovering methods. It also supports using a different class instance and class loader instance for each test and reports all errors on a test-by-test basis. The team has defined three discrete goals for the framework:

The framework must help us write useful tests.

The framework must help us create tests that retain their value over time.

The framework must help us lower the cost of writing tests by reusing code.


Setting up JUnit
To use JUnit to write your application tests, you need to know about its dependencies.

Version 5 of the testing framework is a modular one; you can no longer simply add a jar file to your project compilation classpath and your execution classpath. In fact, starting with version 5, the architecture is no longer monolithic (as discussed in chapter 3).

With the introduction of annotations in Java 5, JUnit has also moved to using them. JUnit 5 is heavily based on annotations--a contrast with the idea of extending a base class for all testing classes and using naming conventions for all testing methods to match the textXYZ pattern, as done in previous versions.

NOTE If you are familiar with JUnit 4, you may wonder what’s new in this version, as well as why and how to move toward it. JUnit 5 represents the next generation of JUnit. You’ll use the programming capabilities introduced starting with Java 8; you’ll be able to build tests modularly and hierarchically; and the tests will be easier to understand, maintain, and extend. Chapter 4 discusses the transition from JUnit 4 to JUnit 5 and shows that the projects you are working on may benefit from the great features of JUnit 5. As you’ll see, you can make this transition smoothly, in small steps.

To manage JUnit 5’s dependencies efficiently, it’s logical to work with the help of a build tool. In this book, we’ll use Maven, a very popular build tool. Chapter 10 is dedicated to the topic of running JUnit tests from Maven. What you need to know now are the basic ideas behind Maven: configuring your project through the pom.xml file, executing the mvn clean install command, and understanding the command’s effects.

NOTE You can download Maven from https://maven.apache.org. When this book was being written, the latest version was 3.6.3.

The dependencies that are always needed in the pom.xml file are shown in the following listing. In the beginning, you need only junit-jupiter-api and junit-jupiter-engine.

Listing 1.4 pom.xml JUnit 5 dependencies

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.0</version>
<scope>test</scope>
</dependency>
To be able to run tests from the command prompt, make sure your pom.xml configuration file includes a JUnit provider dependency for the Maven Surefire plugin. Here’s what this dependency looks like.

Listing 1.5 Maven Surefire plugin configuration in pom.xml

<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
</plugins>
</build>
As Windows is the most commonly used operating system (OS), our example configuration details use Windows 10, the latest version. Concepts such as the path, environment variables, and the command prompt also exist in other OSs; follow your documentation guidelines if you will be running the examples on an OS other than Windows.

To run the tests, the bin folder from the Maven directory must be on the OS path (figure 1.1). You also need to configure the JAVA_HOME environment variable on your OS to point to the Java installation folder (figure 1.2). In addition, your JDK version must be at least 8, as required by JUnit 5.



Figure 1.1 The configuration of the OS path must include the Apache Maven bin folder.



Figure 1.2 The configuration of the JAVA_HOME environment variable

You will need the source files from the chapter to get the results shown in figure 1.3. Open a command prompt into the project folder (the one containing the pom.xml file), and run this command:

mvn clean install
This command will take the Java source code, compile it, test it, and convert it into a runnable Java program (a jar file, in our case). Figure 1.3 shows the result of the test.



Figure 1.3 Execution of the JUnit tests using Maven and the command prompt

Part 3 of the book provides more details about running tests with the Maven and Gradle build tools.

1.4  Testing with JUnit
JUnit has many features that make writing and running tests easy. You’ll see these features at work throughout this book:

Separate test class instances and class loaders for each unit test to prevent side effects

Annotations and JUNIT

JUnit annotations to provide resource initialization and cleanup methods: @BeforeEach, @BeforeAll, @AfterEach, and @AfterAll (starting from version 5); and @Before, @BeforeClass, @After, and @AfterClass (up to version 4)

A variety of assert methods that make it easy to check the results of your tests

Integration with popular tools such as Maven and Gradle, as well as popular integrated development environments (IDEs) such as Eclipse, NetBeans, and IntelliJ

Without further ado, the next listing shows what the simple Calculator test looks like when written with JUnit.

Listing 1.6 JUnit CalculatorTest program

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
public class CalculatorTest { ①
@Test ②
public void testAdd() {
Calculator calculator = new Calculator(); ③
double result = calculator.add(10, 50); ④
assertEquals(60, result, 0); ⑤
}
}
Running such a test with the help of Maven results in behavior similar to that shown in figure 1.3. The test is very simple. At ①, you define a test class. It’s common practice to end the class name with Test. JUnit 3 required extending the TestCase class, but this requirement was removed with JUnit 4. Also, up to JUnit 4, the class had to be public; starting with version 5, the top-level test class can be public or package-private, and you can name it whatever you want.

At ②, you mark the method as a unit test method by adding the @Test annotation. In the past, the usual practice was to name test methods following the testXYZ pattern, as was required up to JUnit 3. Now that doing so is no longer required, some programmers drop the prefix and use a descriptive phrase as the method name. You can name your methods as you like; as long as they have the @Test annotation, JUnit will execute them. The JUnit 5 @Test annotation belongs to a new package, org.junit.jupiter.api, and the JUnit 4 @Test annotation belongs to the org.junit package. This book uses JUnit 5’s capabilities except in some clearly emphasized cases (such as to demonstrate migration from JUnit 4).

At ③, you start the test by creating an instance of the Calculator class (the object under test). And at ④, as before, you execute the test by calling the method to test, passing it two known values.

At ⑤, the JUnit framework begins to shine! To check the result of the test, you call an assertEquals method, which you imported with a static import on the first line of the class. The Javadoc for the assertEquals method is

/**
* Assert that expected and actual are equal within the non-negative delta.
* Equality imposed by this method is consistent with Double.equals(Object)
* and Double.compare(double, double). */
public static void assertEquals(
double expected, double actual, double delta)
In listing 1.6, you pass these parameters to assertEquals:

expected = 60
actual = result
delta = 0
Because you pass the calculator the values 10 and 50, you tell assertEquals to expect the sum to be 60. (You pass 0 as delta because you are expecting no floating-point errors when adding 10 and 50, as the decimal part of these numbers is 0.) When you call the calculator object, you save the return value in a local double named result. Therefore, you pass that variable to assertEquals to compare with the expected value (60). If the actual value is not equal to the expected value, JUnit throws an unchecked exception, which causes the test to fail.

Most often, the delta parameter can be 0, and you can safely ignore it. This parameter comes into play with calculations that are not always precise, including many floating-point calculations. delta provides a range factor: if the actual value is within the range expected - delta and expected + delta, the test will pass. You may find this useful when you’re performing mathematical computations with rounding or truncating errors or when you’re asserting a condition about the modification date of a file, because the precision of these dates depends on the OS.

The remarkable thing about the JUnit CalculatorTest class in listing 1.6 is that the code is easier to write than the first CalculatorTest program in listings 1.2 or 1.3. In addition, you can run the test automatically through the JUnit framework.

When you run the test from the command line (figure 1.3), you see the amount of time it takes and the number of tests that passed. There are many other ways to run tests, from IDEs and from different build tools. This simple example gives you a taste of the power of JUnit and unit testing.

You may modify the Calculator class so that it has a bug--for example, instead of adding the numbers, it subtracts them. Then you can run the test and watch what the result looks like when a test fails.

In chapter 2, we will take a closer look at the JUnit framework classes (annotations and assertion mechanisms) and capabilities (nested and tagged tests, as well as repeated, parameterized, and dynamic tests). We’ll show how t/hey work together to make unit testing efficient and effective. You will learn how to use the JUnit 5 features in practice and differences between the old-style JUnit 4 and JUnit 5.

Summary:

Why every developer should perform some type of test to see if code actually works. Developers who use automatic unit tests can repeat these tests on demand to ensure that new code works and does not break existing tests.

Writing simple unit tests, which are not difficult to create without JUnit.

As tests are added and become more complex, writing and maintaining tests becomes more difficult.

Introduction to JUnit as a unit testing framework that makes it easier to create, run, and revise unit tests.

Stepping through a simple JUnit test.

1.Test-infected is a term coined by Erich Gamma and Kent Beck in “Test-Infected: Programmers Love Writing Tests,” Java Report 3 (7), 37-50, 1998.

2.Ralph Johnson and Brian Foote, “Designing Reusable Classes,” Journal of Object-Oriented Programming 1 (2): 22-35, 1988; www.laputan.org/drc/drc.html.

3.Erich Gamma et al., Design Patterns (Reading, MA: Addison-Wesley, 1995).

4.IEEE Standard Computer Dictionary: A Compilation of IEEE Standard Computer Glossaries (New York: IEEE, 1990).

5.Kent Beck, Extreme Programming Explained: Embrace Change (Reading, MA: Addison-Wesley, 1999)./
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.