Debugging and Unit Testing
Debugging is a big part of software development. To effectively debug, you must be able to “think” like a computer and dive into the code, deconstructing every step that lead to the logic error that you’re working to resolve. In the beginning of computer programming, there weren’t a lot of tools to help in debugging. Mostly, debugging involved taking a look at your code and spotting inconsistencies; then resubmitting the code to be compiled again. Today, every IDE offers the ability of using breakpoints and inspecting memory variables, making it much easier to debug.
Outside the IDE there are other tools that help in daily debugging, building, and testing of your project; and these tools ensure that your code is being continually tested for errors introduced when programming. In this chapter, you explore the different tools that will help aid in debugging, analyzing, and testing Java software.
This chapter covers some debugging and unit testing basics. You will learn how to perform unit testing from the command-line or terminal using Apache Ant, along with JUnit. You will also learn how to make use of the NetBeans Profiler, among other tools, for profiling and monitoring your applications.
11-1. Understanding Exceptions
Problem
You caught and logged an exception, and you need to determine its cause.
Solution
Analyze the output from the exception’s printStackTrace() method:
try {
int a = 5/0;
} catch (Exception e) { e.printStackTrace();
}
Result:
java.lang.ArithmeticException: / by zero
at org.java8recipes.chapter11.recipe11_01.Recipe11_1.start(Recipe11_1.java:18) at org.java8recipes.chapter11.recipe11_01.Recipe11_1.main(Recipe11_1.java:13)
CHAPTER 11 N DEBUGGING AND UNIT TESTING
How It Works
In programming lingo, a stack refers to the list of functions that were called to get to a point in your program, usually starting from the immediate (System.out.println()) to the more general (public static void main). Every program keeps track of which code was executed in order to reach a specific part of the code. Stack trace’s output refers to the stack that was in memory when an error occurred. Exceptions thrown in Java keep track of where they occurred and which code path was executed when the exception was thrown. Stack trace shows from the most specific place where the exception happened (the line where the exception occurred) to the top-level invoker of the offending code (and everything in between). This information then allows you to pinpoint which method calls were performed, and may help shed some light on why the exception was thrown.
In this example, the divide-by-zero exception occurred on line 18 of Recipe11_1.java and was caused by a call from the main() method (at line 13). Sometimes, when looking at the stack trace’s output , you will see methods that don’t belong to the project. This happens naturally as sometimes method calls are generated in other parts of a working system. It is, for example, very common to see AWT methods in Swing applications when an exception is raised (due to the nature of the EventQueue). If you look at the more specific function calls (earliest), you will eventually run with the project’s own code and can then try to determine why the exception was thrown.
Note
N The Stack trace output will contain line number information if the program is compiled with “Debug” info. By default, most IDEs will include this information when running in a Debug configuration.
11-2: Locking Down Behavior of Your Classes
Problem
You need to lock down the behavior of your class and want to create unit tests that will be used to verify the specific behavior in your application.
Solution
Use JUnit to create unit tests that verify behavior in your classes. To use this solution, you need to include the JUnit dependencies in your class path. JUnit can be downloaded from http://www.junit.org. Once it’s downloaded, add the junit-xxx.jar and junit-xxx-dep.jar files to your project or class path (where xxx is the downloaded version number). When JUnit becomes part of your project, you will be able to include the org.junit and junit.framework namespaces.
In this example two unit tests are created for the MathAdder class. The MathAdder class contains two methods:
addNumber (int, int) and substractNumber (int,int). These two methods return the addition (or subtraction) of their passed parameters (a simple class). The unit tests (marked by the @Test annotation) verify that the MathAdder class does, in fact, add and/or subtract two numbers.
package org.java8recipes.chapter11;
import junit.framework.Assert;
import org.junit.Test;
CHAPTER 11 N DEBUGGING AND UNIT TESTING
public class Recipe11_2_MathAdderTest {
@Test
public void testAddBehavior() {
Recipe_11_2_MathAdder adder = new Recipe_11_2_MathAdder();
for (int i =0;i < 100;i++) { for (int j =0;j < 100;j++) {
Assert.assertEquals(i+j,adder.addNumbers(i,j));
} } }
@Test
public void testSubstractBehavior() {
Recipe_11_2_MathAdder adder = new Recipe_11_2_MathAdder();
for (int i =0;i < 100;i++) { for (int j =0;j < 100;j++) {
Assert.assertEquals(i-j,adder.substractNumber(i,j));
} } } }
To execute this test, use your IDE to run the test class. For example, in NetBeans, you must refactor the test class to place it into the “Test Packages” module within the NetBeans project. Once you’ve moved the test class into the desired package within “Test Packages,” run the file to perform the tests.
How It Works
Unit tests are useful for testing your code to ensure that expected behaviors occur within your classes. Including unit tests in your project makes it less likely to break functionality when adding or refactoring code. When you create unit tests, you are specifying how an object should behave (what is referred to as its contract). The unit tests ensure that the expected behavior occurs (they do this by verifying the result of a method and using the different JUnit.Assert methods).
The first step to writing a unit test is to create a new class that describes the behavior you want to verify. One of the general unit–test naming conventions is to create a class with the same name as the class being tested with the postfix of Test; in this recipe’s example, the main class is called Recipe11_2_MathAdder, while the testing class is called Recipe11_2_MathAdderTest.
The unit test class (MathAdderTest) will contain methods that check and verify the behavior of the class. To do so, method names are annotated. Annotations are forms of metadata, and a developer can “annotate” specified portions of code, thereby adding information to the annotated code. This extra information is not used by the program, but by the compiler/builder (or external tools) to guide the compilation, building, and/or testing of the code. For unit-testing purposes, you annotate the methods that are part of the unit test by specifying @Test before each method name.
Within each method, you use Assert.assertEquals (or any of the other Assert static methods) to verify behavior.
The Assert.assertEquals method instructs the unit-testing framework to verify that the expected value of the method call from the class that you are testing is the same as the actual value returned by its method call. In the recipe example, Assert.assertEquals verifies that the MathAdder is correctly adding the two integers. While the scope of this class is trivial, it shows the bare minimum requirements to have a fully functional unit test.
CHAPTER 11 N DEBUGGING AND UNIT TESTING
If the Assert call succeeds, it gets reported in the unit test framework as a “passed” test; if the Assert call fails, then the unit test framework will stop and display a message showing where the unit test failed. Most modern IDEs have the capability of running unit test classes by simply right-clicking the name and selecting Run/Debug (and that’s the intended way of running the Chapter_11_2_MathAdderTest recipe).
While it is true that IDEs can run unit tests while developing, they are created with the intention of being run automatically (usually triggered by a scheduled build or by a version control system’s check-in), which is what the Recipe 11-3 talks about.
11-3. Scripting Your Unit Tests
Problem
You want to automatically run your unit tests, rather than manually invoke them.
Solution
Use and configure JUnit and Ant. To do so, follow these steps:
1. Download Apache Ant (located at http://ant.apache.org/).
2. Uncompress Apache Ant into a folder (for example, c:\ant for Windows systems or /Development for OS X).
3. Make sure that Apache Ant can be executed from the command-line or terminal. In Windows, this means adding the apache-ant/bin folder to the path as follows:
a. Go to Control Panel ° System.
b. Click Advanced system settings.
c. Click Environment Variables.
d. In the System Variables list, double-click the variable name PATH.
e. At the end of the string, add ;C:\apache-ant-1.8.2\bin (or the folder that you uncompressed Apache Ant into).
f. Click OK (on each of the popup boxes that were opened before) to accept the changes.
Note
N Apache Ant comes pre-installed on OS X, so you do not have to install or configure it. To verify this, open a terminal window and type ant –version to see which version is installed on the system.
Make sure that the JAVA_HOME environment variable is defined. In Windows, this means adding a new environment variable called JAVA_HOME. For example:
Go to Control Panel ° System.
4. Click Advanced system settings.
5. Click Environment Variables. In the System Variables list, check to see whether there is
CHAPTER 11 N DEBUGGING AND UNIT TESTING On OS X, environment variables are set up within the .bash profile file, which resides within the user home directory. To add JAVA_HOME, add a line such as the following to the .bash_profile:
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home
Test that you can reach Ant, and that Ant can find your JDK installation. To test that the changes took effect, do the following:
6. Open a command window or terminal.
7. Type ANT.
If you receive the message "Ant is not recognized as an internal or external command", redo the first steps of setting up the PATH variable (the first set of instructions). If you receive the message "unable to locate tools.jar", you need to create and/or update the JAVA_HOME path for your installation (the second set of instructions).
The message "Buildfile: build.xml does not exist!" means that your setup is ready to be built using Ant. Congratulations!
Note
N When changing environment variables in Microsoft Windows, it is necessary to close previous command-line windows and reopen them because changes are only applied to new command windows. To open a command window in Microsoft Windows, click Start, type CMD, and press Enter.
Create build.xml at the root of your project and put the following bare-bones Ant script as the contents of the build.xml file. This particular build.xml file contains information that Ant will use to compile and test this recipe.
<project default="test" name="Chapter11Project" basedir=".">
<property name="src" location="src"/>
<property name="build" location="build/"/>
<property name="src.tests" location="src/"/>
<property name="reports.tests" location="report/" />
<path id="build.path">
<fileset dir="dep">
<include name="**/*.jar" />
</fileset>
<pathelement path="build" />
</path>
<target name="build">
<mkdir dir="${build}" />
<javac srcdir="${src}" destdir="${build}">
<classpath refid="build.path" />
</javac>
</target>
<target name="test" depends="build">
<mkdir dir="${reports.tests}" />
<junit fork="yes" printsummary="yes" haltonfailure="yes">
<classpath refid="build.path" />
CHAPTER 11 N DEBUGGING AND UNIT TESTING
<batchtest fork="yes" todir="${reports.tests}">
<fileset dir="${src.tests}">
<include name="**/*Test*.java"/>
</fileset>
</batchtest>
</junit>
</target>
</project>
Note
N To execute this recipe, open a command-line window or terminal, navigate to the Chapter 11 folder, type ant, and press Enter.
How It Works
Apache Ant (or simply Ant) is a program that allows you to script your project’s build and unit testing. By configuring Ant, you can build, test, and deploy your application using the command-line. (In turn, it can be scheduled to be run automatically by the operating system.) Ant can automatically run unit tests and report on the result of these tests.
These results can then be analyzed after each run to pinpoint changes in behavior.
Due to Ant’s complexity, it has a large learning curve, but it allows for a lot of flexibility on compiling, building, and weaving code. By using Ant, it is possible to achieve the utmost configuration on how your project is built.
Note
N Visit http://ant.apache.org/manual/index.html for a more in-depth tutorial of Ant.
The build.xml file contains instructions on how to compile your project, which class path to use, and what unit tests to run. Each build.xml contains a <project> tag that encapsulates the steps to build the project. Within each
<project> there are targets, which are “steps” in the build process. A <target> can depend on other targets, allowing you to establish dependencies in your project (in this recipe’s example, the target “test” depends on the target “build,”
meaning that to run the test target, Ant will first run the build target).
Each target contains tasks. These tasks are extensible, and there is a core set of tasks that you can use out of the box. The <javac>task will compile a set of Java files specified within the src attribute and write the output to the dest attribute. As part of the <javac> task, you can specify which class path to use. In this example, the class path is specified by referring to a previously defined path, known as build.path. Ant provides ample support for creating class paths. In this recipe, the class path is defined as any file that has the .jar extension located in the dep folder.
The other task in the build target is <junit>. This task will find a unit test specified in its task and run it. The unit tests are defined within the <batchtest> property. By using the <fileset> property, it is possible to tell JUnit to find any file that has the word Test in its name and ends with the .java extension. Once JUnit runs each test, it will write out a summary to the console and write a report on the results of the unit tests to the reports.tests folder.
Note
N You can define variables in a build.xml file by using the <property> tag. Once a property is defined, it can be accessed as part of another task using the ${propertyName} syntax. This allows you to quickly change a build script in response to structural changes (for example, switching target/source folders around).
CHAPTER 11 N DEBUGGING AND UNIT TESTING
11-4. Finding Bugs Early
Problem
You want to ensure that you are able to find the maximum number of bugs at design time.
Solution
Use FindBugs to scan your software for issues. Use an Ant build file that includes FindBugs for reporting purposes.
The following is the new build.xml file that adds FindBugs reporting:
<project default="test" name="Chapter11Project" basedir=".">
<property name="src" location="src"/>
<property name="build" location="build/"/>
<property name="reports.tests" location="report/" />
<property name="classpath" location="dep/" />
<!-- Findbugs Static Analyzer Info -->
<property name="findbugs.dir" value="dep/findbugs" />
<property name="findbugs.report" value="findbugs" />
<path id="findbugs.lib" >
<fileset dir="${findbugs.dir}" includes="*.jar"/>
</path>
<taskdef name="findbugs" classpathref="findbugs.lib" classname="edu.umd.cs.findbugs.anttask.
indBugsTask"/>
<path id="build.path">
<fileset dir="dep">
<include name="**/*.jar" />
</fileset>
</path>
<target name="clean">
<delete dir="${build}" />
<delete dir="${reports.tests}" />
<delete dir="${coverage.dir}" />
<delete dir="${instrumented}" />
<mkdir dir="${build}" />
<mkdir dir="${reports.tests}" />
<mkdir dir="${coverage.dir}" />
</target>
<target name="build">
<javac srcdir="${src}" destdir="${build}" debug="${debug}">
<classpath refid="build.path" />
</javac>
</target>
<target name="test" depends="clean,build">
CHAPTER 11 N DEBUGGING AND UNIT TESTING
<formatter type="plain"/>
<batchtest fork="yes" todir="${reports.tests}">
<fileset dir="${build}">
<include name="**/*Test*.class"/>
</fileset>
</batchtest>
<jvmarg value="-XX:-UseSplitVerifier" />
</junit>
</target>
<target name="findbugs" depends="clean">
<antcall target="build">
<param name="debug" value="true" />
</antcall>
<mkdir dir="${findbugs.report}" />
<findbugs home="${findbugs.dir}"
output="html"
outputFile="${findbugs.report}/index.html"
reportLevel="low"
>
<class location="${build}/" />
<auxClasspath refid="build.path" />
<sourcePath path="${src}" />
</findbugs>
</target>
</project>
To run this recipe, download FindBugs (http://findbugs.sourceforge.net/downloads.html). Uncompress into a folder in your computer, then copy the contents of the ./lib/ folder into your project’s /dep/findbugs folder (create the /dep/findbugs folder if necessary). Make sure that /dep/findbugs/findbugs.jar and /dep/findbugs/
findbugs-ant.jar are present.
How It Works
FindBugs is a Static Code Analyzer (SCA). It will parse your program’s compiled file and spot errors in coding (not syntax errors, but certain types of logic errors). As an example, one of the errors that FindBugs will spot is comparing two strings using == instead of String.equals(). The analysis is then written as HTML (or text) that can be viewed with a browser. Catching errors from FindBugs is easy, and adding it as part of your continuous integration process is extremely beneficial.
At the beginning of build.xml, you define the FindBugs tasks. This section specifies where the .jar files are that define the new task (dep\findbugs), and also determines where to put the report when done.
The build.xml also has a new target project called “findbugs.” The findbugs target compiles the source files with debug information (having debug information helps on the FindBugs report as it will include the line number when reporting errors), and then proceeds to analyze the byte-code for errors. In the findbugs task, you specify the location of the compiled .class files (this is the <class> property), the location of the dependencies for your project (<auxClasspath> property), and the location of the source code (<sourcePath> property).