Setting the table
This book is based on a hands-on example that will guide us through the essential concepts and programming techniques of unit testing. For a sustainable learning experience, feel encouraged to elaborate and complete the various code snippets in your own working environment. Hence, here comes a short introduction of the most important tools and the workspace organization used while programming the sample.
Choosing the ingredients
As the book's title implies, the main tool this is all about is JUnit (http://www.junit.org). It is probably the most popular testing framework for developers within the Java world. Its first version was written by Kent Beck and Eric Gamma on a flight from Zurich to OOPSLA 1997 in Atlanta, [FOWL06]. Since then, it has evolved by adapting to changing language constructs, and quite a few supplemental libraries have emerged.
Java IDEs provide a UI and build path entries to compile, launch, and evaluate JUnit tests. Build tools, such as Ant, Maven, and Gradle, support test integration out of the box. When it comes to IDEs, the example screenshots in this book are captured using Eclipse (http://www.eclipse.org/). However, we do not rely on any Eclipse-specific features, which should make it easy to reproduce the results in your favorite IDE too.
In general, we use
Maven (https://maven.apache.org/) for dependency management of the libraries mentioned next, which means that they can be retrieved from the Maven
Central Repository (http://search.maven.org/). But if you clone the book's GitHub repository (https://github.com/fappel/Testing-with-JUnit), you will find a separate folder for each chapter, providing a complete project configuration with all dependencies and sources. This means navigating to this directory and using the 'mvn test'
Maven command should enable you to compile and run the given examples easily. Let's finish this section with an introduction of the more important utilities we'll be using in the course of the book.
Chapter 3, Developing Independently Testable Units, covers the sense and purpose of the various test double patterns. It is no wonder that there are tools that simplify test double creation significantly. Usually, they are summarized under the term mock frameworks. The examples are based on Mockito (http://mockito.org), which suits very well to building up clean and readable test structures.
There are several libraries that claim to improve your daily testing work. Chapter 5, Using Runners for Particular Testing Purposes, will introduce JUnitParams (http://pragmatists.github.io/JUnitParams/) and Burst (https://github.com/square/burst) as alternatives to writing parameterized tests. Chapter 7, Improving Readability with Custom Assertions, will compare the two verification tools Hamcrest (http://hamcrest.org/) and AssertJ (http://assertj.org).
Automated tests are only valuable if they are executed often. Because of this, they are usually an inherent part of each project's continuous integration build. Hence, Chapter 8, Running Tests Automatically within a CI Build, will show how to create a basic build with Maven and introduce the value of code coverage reports with JaCoCo (http://www.eclemma.org/jacoco/).
Organizing your code
In the beginning, one of the more profane-looking questions you have to agree upon within your team is where to put the test code. The usual convention is to keep unit tests in classes with the same name as the class under test, but post- or prefixed with an extension Test
or such like. Thus, a test case for the Foo
class might be named FooTest
.
Based on the description of Hunt/Thomas, [HUTH03], of different project structuring types, the simplest approach would be to put our test into the same directory where the production code resides, as shown in the following diagram:
We usually don't want to break the encapsulation of our classes for testing purposes, which shouldn't be necessary in most cases anyway. But as always, there are exceptions to the rule, and before leaving a functionality untested, it's probably better to open up the visibility a bit. The preceding code organization provides the advantage that, in such rare cases, one can make use of the package member access the Java language offers.
Members or methods without visibility modifiers, such as public, protected, and private, are only accessible from classes within the same package. A test case that resides in the same package can use such members, while encapsulation still shields them from classes outside the package, even if such classes would extend the type under test.
Unfortunately, putting tests into the same directory as the production code has a great disadvantage too. When packages grow, the test cases are perceived soon as clutter and lead to confusion when looking at the package's content. Because of this, another possibility is to have particular test subpackages, as shown here:
However, using this structure, we give up the package member access. But how can we achieve a better separation of production and testing code without loosing this capability? The answer is to introduce a parallel source tree for test classes, as shown here:
To make this work, it is important that the root of both trees are part of the compiler's CLASSPATH settings. Luckily, you usually do not have to put much effort in this organization as it is the most common one and gets set up automatically, for example, if you use Maven archetypes to create your projects. Examples in this book assume this structure.
Last but not least, it is possible to enhance the parallel tree concept even further. A far-reaching separation can be achieved by putting tests in their own source code project. The advantage of this strategy is the ability to use different compiler error/warning settings for test and production code. This is useful, for example, if you decide to avoid auto-boxing in your components but feel it would make test code overly verbose when working with primitives. With project-specific settings, you can have hard compiler errors in production code without having the same restriction in tests.
Whatever organization style you may choose, make sure that all team members use the same one. It will be very confusing and hardly maintainable, if the different concepts get mixed up. Now that the preliminaries are done, we are ready for action.