Chapter 3. Unit Testing with JUnit and Debugging

By now, you are past the basics and should be familiar with developing Java applications using the Eclipse IDE. Although you must be feeling very confident with your recent acquired skills, before we go any further, you should know that the more you code, the higher are the chances of you introducing a bug into your code. Even with all the facilities provided by Eclipse, things are bound to go wrong as Java applications scale up. So, we need to build a safety net whenever our application code starts to become nontrivial to manage. One way of doing this is to test our programs, and testing is one of the things that we are going to cover in this chapter.

In an ideal world, all problems detected by our tests would be easy to track down and fix. Nevertheless, this is not always the case. It turns out that nasty bugs might make their way into our code. As mentioned, having a good set of tests helps us to spot problems, however, sometimes the feedback provided by the test set is not enough, and we need to dig deeper to uncover problems. The act of understanding why the software is not behaving properly and working out a fix for it is called debugging. Debugging is covered later in this chapter.

When it comes to testing and debugging Java programs, Eclipse has got you covered. It comes with built-in features for automating the creation and execution of JUnit tests, and it features an impressive Java debugger.

We will cover the following topics in this chapter:

  • Configuring JUnit inside Eclipse
  • Organizing, developing, and running unit tests using JUnit inside Eclipse
  • Using Eclipse's built-in debugger when bugs arise and understanding that only test methods are not enough to pinpoint the problem cause

Testing with JUnit – getting started

Before describing JUnit, we believe that some words about unit testing are in order. When carrying out unit testing activities, emphasis is given to small chunks of code. In other words, you are not trying to cover the whole program with a single test. Rather, you are testing the foundations of your programs by writing a suite of tests per class, one or more tests per method. Writing tests while you are working on the code at hand has a host of benefits. For instance, it is easier to find bugs when unit testing. This is the case because each unit test should evaluate exactly one functional unit, which makes the scope one has to hunt for bugs significantly smaller than searching the whole program for bugs. However, according to Robert C. Martin, to reap the full benefits of unit testing, your test suite has to comply with the following principles (also known as the FIRST acronym):

  • Fast: The tests should be fully automatic and should be able to check their own results. But most importantly, you should be able to execute them fast so that they can be frequently executed.
  • Independent: The tests should not depend on each other you should be able to run your tests in any particular order.
  • Repeatable: You should be able to execute your tests in any environment (not only production) and as frequently as required.
  • Self-validating: The tests should produce a Boolean outcome, that is, they should either pass or fail; there should be nothing in between. In fact, it boils down to avoiding subjective outputs that require manual evaluation.
  • Timely: According to Robert C. Martin, unit tests should be written before the code implements the feature that they evaluate. According to him, it is harder to write tests after implementing the production code. From an academic perspective, there is no substantial evidence regarding the benefits of writing tests before, in comparison to writing tests after implementing the code that they evaluate. This is clearly a controversial topic, we do not seek to influence you in favor of a particular approach. Try writing tests before and after implementing the code and see which works best for you.

    Note

    In case you think that writing tests before implementing code suits you better, you should read about Test Driven Development (TDD). While you are at that, you might also want to study refactoring, which consists of improving your code without changing its external behavior.

Now that you have a basic understanding of the philosophy that backs up unit testing, you are ready to take the next step towards writing your own tests—learning how to use a unit testing framework. Since JUnit is arguably the most popular testing framework in the Java community, we will cover it here.

More specifically, JUnit is an open source testing framework, which was initially contrived by Kent Beck and Erich Gamma (www.junit.org). As of the time of this writing, the latest stable version of this framework (and the one bundled with the IDE) is Version 4.10. Due to the fact that Erich led the design of the Eclipse Java Development Tools (JDT), JUnit is well-integrated into the IDE. Eclipse already ships with a bundled JUnit JAR. So, as long as you are planning on using JUnit 4.10, there is not much to say about installing it. However, it is also possible to use a different JUnit version. The steps for setting up the default and a non-default JUnit JAR are covered in the following sections.

Although the duo, JUnit and Eclipse, supports and automates most of the testing process, you still need to write your own test files manually. Since Eclipse leaves the organization of your test files up to you, a word about how to organize these files is in order.

JUnit-based test files can be organized in the following way. Suppose your application has a class named MyAppClass, and you want to test it. To do so, you need to place your tests in another class named MyAppClassTest. Usually, test classes are kept separated from regular code. The convention is placing them into a subpackage called test. So, let's say that MyAppClass is in a package called org.myapp, tests related to this class need to be placed in the subpackage named org.myapp.test. As for the tests themselves, they are grouped in MyAppClassTest in the form of Java methods with some slight modifications that we will describe shortly. An overview of this typical organization is shown in the following screenshot, which illustrates this organization using a hypothetical Java project named MyTestProject:

Testing with JUnit – getting started

Note

It is worth mentioning that neither JUnit nor Eclipse enforces this naming convention. The creation of separate folders/subpackages for tests is not mandatory either. However, following these conventions is a good programming practice. It is also worth mentioning that another widely used convention to organize test code is the maven convention. By following this convention, you need to create separate folders for regular Java code and test code, src/main/java and src/test/java. Also, the test classes are placed in the same package in which the classes they test are placed.

Setting up JUnit

In order to use JUnit with Eclipse, you need to add junit.jar to your project's build path. To do this, right-click on your project, and navigate to Build Path | Add Libraries…, as shown in the following screenshot:

Setting up JUnit

The following screenshot shows the options listed by Eclipse. Select the second option JUnit. You can select either JUnit Version 3 or Version 4.

Setting up JUnit

Throughout this chapter we are going to use JUnit 4 (4.10 to be more specific); so go ahead and select JUnit 4 as shown in the following screenshot:

Setting up JUnit

There is a striking difference between JUnit 3 and JUnit 4, mostly due to the fact that JUnit 4 heavily relies on the new features introduced by the most recent versions of Java. To be more specific, the main difference between JUnit 3 and JUnit 4 is that the latter relies on annotations rather than hierarchies and method-name conventions. If you are familiar with JUnit 3, but have not used JUnit 4 yet, you will notice that the latter is simpler and easier to use than the former.

By choosing JUnit 4, Eclipse adds JUnit 4.10 to your project's build path. If you need to use a different JUnit version, you can download that particular version from JUnit's website and add it to your project's build path. To do that, download the desired version from JUnit's website, right-click on your project name, and navigate to Build Path | Configure Build Path…, as illustrated in the following screenshot:

Setting up JUnit

The project properties window will appear, showing the project's build path current configurations (shown in the following screenshot). Select the Libraries tab, click on Add External JARs…, locate and select your recently downloaded JAR, and then click on OK. Click on OK again to close the properties window. Done, now you have JUnit up and running. We are all set to create JUnit-based tests. It is worth mentioning that through the project properties window you can also add JUnit 4 by clicking on the Add Library… option. Selecting this option opens the pop-up window shown in the earlier screenshot.

Setting up JUnit

Adding a new JAR to the build path can be done in a myriad of ways. In this section we just covered two. If you followed the first approach, the JUnit JAR file is going to be placed under JUnit 4 as shown in (a) in the following screenshot. Otherwise, the JAR file is going to appear under Referenced Libraries as shown in (b). Keep on reading to discover an even easier approach for setting up JUnit.

Setting up JUnit

Testing with JUnit

Using JUnit, it is remarkably simple to write repeatable tests. Actually, from day one, being simple to use was a key design decision made by Kent and Erich. According to them, if JUnit was not easy to learn and execute, programmers would not actually use it.

Basically, when you need to test something using JUnit, you perform these basic actions:

  1. Create the Java class that is going to enclose all your tests. Make sure to name it according to the convention we described in the previous section.
  2. Create a method and annotate it with @org.junit.Test.
  3. Write the code related to the behavior that you want to test inside the previously created method.
  4. To check whether the code under test yielded the expected result, import org.junit. Assert statically, and invoke one of the methods of the Assert family (assertTrue, assertEquals, and so on) to compare the result of your test with the expected result.

The JUnit framework has several other features, but these are the ones you will need to understand in order to get started. So, let's dive into the technical details of these basic features by testing a Java class that implements a basic calculator.

First, we need a new Java project for our calculator class. So, inside Eclipse navigate to File | New | Java Project. To give our project a name, type BasicCalculatorProject in the Project name field, and then click on Finish. You might be prompted to switch to the Java Perspective. If this happens, answer Yes. After performing all these steps, the Package Explorer view should show your new Java project. However, if something goes wrong and you can run into problems, go back to the previous chapter and review how to create a Java project.

Inside our recently created Java project, create a new package. To create a package named chapter3.basiccalc, select the src folder, right-click on it, and navigate to New | Package. Enter the package name, and click on the Finish button.

Let's create a class named BasicCalculator. Right-click on your recently created package and navigate to New | Class. Our class under test is as follows:

package chapter3.basiccalc;

public class BasicCalculator {
  private static int[] tempVars; //acts as an array of temp variables
  private static int result;
  
  public void add(int n) {
    result += n; 
  }

  public void subtract(int n) {
    result -= n;
  }

  public void addResultToTempAt(int index) {
    tempVars[index] = result;// BUG: tempVars wasn't initialized properly
  }

  public void squareRoot(int n) {
    //not implemented yet
  }

  public void divide(int n) {
    result /= n;
  }

  
  public void multiply(int n) {
    result = n * n;
  }

  public void clear() {
    result = 0;
  }

  public void clearTemps() {
    for (int i =0; i < tempVars.length; i++) {
      tempVars[i] = 0;
    }
  }
  public int getResult() {
    return result;
  }

}

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.PacktPub.com. If you purchased this book elsewhere, you can visit http://www.PacktPub.com/support and register to have the files e-mailed directly to you.

Our calculator implementation contains two faults, but do not mind about them yet. Using a buggy implementation will make our example more interesting.

The purpose of the BasicCalculator class is to perform elementary arithmetic operations (addition, subtraction, multiplication, and division) and calculate the square root; while storing the result after each operation. Apart from the result variable, an array is used to store preliminary results; making it possible to reuse it in subsequent calculations. Methods for reusing the values stored in this array are not implemented yet. So far, our calculator can only store preliminary results (which are kept in the tempVars array) and clean its memory (by assigning zero to all elements of tempVars). Further, notice that the method that should calculate the square root is not implemented either.

Note

Well, our class should calculate the square root, however, as you saw, this particular feature has not been implemented yet. Do not worry about that, we did not implement this particular feature for demonstration purposes. Later in this chapter, we will show you how to make JUnit skip a test case for which either there is no implementation or the current implementation has been acting up.

Since we are sticking to the naming convention we presented in the previous section, before testing BasicCalculator, you should create a package called chapter3.basiccalc.test. The class containing all test cases should be created inside this package.

Now that you have set your Java project up and dealt with all scaffolding around your test code, you can either add JUnit to your project build path following the aforementioned directions, or keep reading and discover an even easier way of adding JUnit to the build path (as we mentioned, there are many ways of doing operations in Eclipse).

Right-click on BasicCalculator and navigate to New | JUnit Test Case. The window shown in the following screenshot will appear:

Testing with JUnit

After selecting the JUnit Test Case option, a new window will pop up with the name of the test case already filled out (BasicCalculatorTest). It also shows the class under test (BasicCalculator). You only need to type the test package name in the Package field. So go ahead and type chapter3.basiccalc.test as we did in the following screenshot (or click on the Browse button and select the desired package).

Testing with JUnit

Implementing the test methods generated by Eclipse

Notice that we uncheck the setUp() and tearDown() options. We are going to explain what these terms mean shortly. But for the time being, we do not want Eclipse to automatically generate these methods for us. After unchecking these options (in case they are not unchecked by default), click on Next and select the methods that you want to test, as demonstrated in the following screenshot. Here Eclipse is prompting you to select the methods for which it should generate stubs.

Implementing the test methods generated by Eclipse

When you click on Finish, if you have not added JUnit to your project build path yet, Eclipse will prompt you for adding JUnit 4 to the build path (as shown in the following screenshot). You should respond by clicking on OK.

Implementing the test methods generated by Eclipse

Eclipse will then generate the following code for you:

package chapter3.basiccalc.test;

import static org.junit.Assert.*;
import org.junit.Test;

public class BasicCalculatorTest {

  @Test
  public void testAdd() {
    fail("Not yet implemented");
  }

  @Test
  public void testSubtract() {
    fail("Not yet implemented");
  }

  @Test
  public void testAddResultToTempAt() {
    fail("Not yet implemented");
  }

  @Test
  public void testSquareRoot() {
    fail("Not yet implemented");
  }

  @Test
  public void testDivide() {
    fail("Not yet implemented");
  }

  @Test
  public void testMultiply() {
    fail("Not yet implemented");
  }

  @Test
  public void testClear() {
    fail("Not yet implemented");
  }

  @Test
  public void testClearTemps() {
    fail("Not yet implemented");
  }
}

As you can see, Eclipse generated a lot of boilerplate code automatically. To be more specific, Eclipse generated all test methods (one for each method of our class under test), annotated then with org.junit.Test (@Test), and statically imported the assert methods.

The @Test annotation is used to indicate to JUnit that the annotated method should be run as a test case. Prior to running each test case, JUnit creates an instance of the class and then invokes the test case method on that instance. Later in this chapter, we will see how JUnit allows programmers to take advantage of this characteristic to initialize the context for test method that needs objects created before they can be executed.

If a test throws an exception, JUnit reports it as a failure. As you might have already figured out, all test method stubs generated by Eclipse are failing test methods. In fact, our test methods are failing because they invoke the fail() method from org.junit.Assert, which signals the failure of a test method by throwing an AssertionError.

In order to execute a JUnit-based test class, right-click on it and navigate to Run As | JUnit Test, as shown in the following screenshot:

Implementing the test methods generated by Eclipse

Following the approach shown just now, try to run BasicCalculatorTest. The results of the tests are shown in the following screenshot in the JUnit Runner view. Whenever you run a JUnit-based class, this view is shown in the current perspective. This view shows you a list of failures and the test suite as a tree. Failing tests are indicated with a red bar. (as shown in the following screenshot, we have eight failing tests). The next sections provide more information on each toolbar of this window.

Implementing the test methods generated by Eclipse

By now, you should be used to the fact that you can perform the same operation in Eclipse in different ways. There is no exception when it comes to running test cases. Here are some of the ways you can run your test cases:

  • Let's say you want to run a single test method. Right-click on that test method from the Outline or Package Explorer, and navigate to Run As | JUnit Test
  • To re-run a single test, choose a test in the JUnit Runner view, right-click on it, and select Run
  • To run all the tests inside a project or a package, select the package, right-click on it, and navigate to Run As | JUnit Test

In order to check out what a passing test looks like, comment the body of the method testAdd() as in the following code snippet, and using the approach described just now, run only testAdd():

@Test
public void testAdd() {
  //fail("Not yet implemented");
}

You should see a green bar; as illustrated in the following screenshot. To be more specific, it shows that testAdd() is a passing test method and the other test methods were not executed.

Implementing the test methods generated by Eclipse

Before implementing tests that really evaluate the behavior of our calculator, it is worth taking a closer look at the JUnit Runner view, because you are going to be interacting with it throughout this chapter.

An overview of the Eclipse JUnit Runner view

After executing the JUnit-based classes, the JUnit Runner view is shown. In addition to indicating whether the recently run tests succeeded or failed, this view also shows why the failing tests failed and some useful toolbar commands. The JUnit Runner view has two panes, the tests pane and the failure trace pane. The tests pane lists all the executed tests as a tree. Upon selecting a failed test, the failure trace pane shows information on why the selected test failed. The following table presents a description of each toolbar command in the tests pane:

Icon

Description

An overview of the Eclipse JUnit Runner view

Moves the selection to the next failing test

An overview of the Eclipse JUnit Runner view

Moves the selection to the previous failing test

An overview of the Eclipse JUnit Runner view

Shows only failing tests, if any

An overview of the Eclipse JUnit Runner view

Keeps the test list from scrolling

An overview of the Eclipse JUnit Runner view

Re-runs all tests

An overview of the Eclipse JUnit Runner view

Re-runs only all failing tests

An overview of the Eclipse JUnit Runner view

Stops the current execution

An overview of the Eclipse JUnit Runner view

Shows test run history

The following table describes the options available in the Failure Trace pane. To navigate from a certain failure to the related source code, double-click on the corresponding line in the Failure Trace pane.

Icon

Description

An overview of the Eclipse JUnit Runner view

Filters stack trace by removing unwanted stack frames from it

An overview of the Eclipse JUnit Runner view

Compares the actual and the expected results of String comparisons

Creating test cases

So far, we have not typed much; Eclipse has done everything for us. However, now it's time for us to do some typing. Aimed at implementing our test cases, we are going to take small steps. First, we need to comment out test cases that have not been implemented yet. It turns out that JUnit provides a better way to disable unfinished test methods, by annotating them with @Ignore. Methods annotated with @Test and @Ignore will not be executed by JUnit at all. JUnit treats them as if they were commented out. When annotating a method with @Ignore, you can also provide a string that explains why the underlying test method is being ignored.

In order to use @Ignore, you have to import org.junit.Ignore. Once imported, @Ignore can be used to annotate the methods, as illustrated in the following code. In the following code, we are telling JUnit to ignore testSubtract, because it has not been properly implemented yet. Go ahead and mark all methods but testAdd() with @Ignore. Executing our test suite now will result in no failures.

@Ignore("Not implemented yet")
@Test
public void testSubtract() {
   fail("Not yet implemented");
}

Now that the other test cases won't get in our way, let's start implementing testAdd(). Fortunately, writing unit tests is very straightforward. You just need to call chunks of your application's code, get the results back, and then check if they are exactly what you expected. Instead of having to write lots of if statements to compare the results when using JUnit, you can rely on the assert methods. Using assert methods, you specify the expected outcome, and then pass the actual result from invoking your application code.

For example, we can implement our testAdd() method by performing simple addition operations as shown in the following code. First, we need to create an instance of BasicCalculator. Second, we invoke the chunk of code that we are trying to test, namely, the add() method. Finally, we use an assert method to evaluate the result of invoking the add() method by passing 2 as parameter twice. The assertEquals() method expects getResult() to return 4. Upon running our test case, we can see that the method under test seems to be working properly since getResult() returned 4, as expected. Here is how we implemented testAdd():

@Test
public void testAdd() {
  BasicCalculator calculator = new BasicCalculator();
  calculator.add(2);
  calculator.add(2);
  // result should be 4
  assertEquals(4 /*expected*/, calculator.getResult() /*actual*/);
  calculator.clear();
}

Note

Usually, writing unit tests should be easy. If writing them is taking too long, it might mean that your design needs some improvement; you should think about refactoring your code.

Before implementing more tests, a few more words about assert methods are in order. As you can see in the previous example, assertEquals() takes two parameters, the expected result and the value to check against the expected result. In our code, we are referring to this method through static import (check out the static import statement that Eclipse autogenerated for us). If we were to use assertEquals() without statically importing it, we would have to do more typing: Assert.assertEquals(4, calculator.getResult()).

The test for subtraction operations is similar to the one for addition operations. Again, we create an instance of the class under test, invoke the chunk of code under test, and check if the invoked code yielded the expected outcome. Go ahead and implement testSubtract() as shown in the following code snippet. Note that our testSubtract() method relies on add() to set the stage for subtract(). More specifically, add() is invoked to store the value 35 into the calculator's result variable. Then we subtract 5 from the result and use an assert method to evaluate whether after performing these arithmetic operations the result contains the value 30.

@Test
public void testSubtract() {
  BasicCalculator calculator = new BasicCalculator();
  calculator.add(35);
  calculator.subtract(5);
  assertEquals(30, calculator.getResult());
  calculator.clear();
}

As you can see, all tests start by creating an instance of BasicCalculator (the one upon which the methods under test are invoked) and conclude with an invocation to calculator.clear() (in order to clear the value stored in result, so that it will not tamper with the results yielded by the other test methods). It is always a good idea to eliminate duplicated code from both your application and test code. We can extract this duplicated code into a method that does all the initialization and configuration of the test environment, and a method that contains the cleanup code. In JUnit 4, in order to have a method executed before each test case, simply denote it with @Before (org.junit.Before). Here is how we can remove part of the duplicated code:

private BasicCalculator calculator;

@Before
public void setup() {
  calculator = new BasicCalculator();
}

@Test
public void testAdd() {
  calculator.add(2);
  calculator.add(2);
  // result should be 4
  assertEquals(4/*expected*/, calculator.getResult()/*actual*/);
  calculator.clear();
}

Note that calculator is now an instance variable, and neither testAdd() nor testSubtract() (not shown in the code snippet) need to instantiate a local version of BasicCalculator; they simply use the calculator instance variable. Also note that all initialization code was moved to the setup method, which JUnit makes sure to invoke before executing each test method. Normally, methods that contain the initialization code needed for exercising the program under test are named setup. Likewise, methods containing the cleanup code are named teardown. Given that each test case now uses its own instance of BasicCalculator, if result were not a static variable, we would not need to invoke clear() on those instances. So, we would be able to safely get rid of all calls to clear(). However, since it is a static variable, we still need to invoke clear() after each test method.

Note

Since methods noted with @Before run before each test case, there are no side effects among test runs.

In our example, the cleanup code should be extracted into a method denoted with @After. By doing so, this method will be executed after each test case. Go ahead and implement the cleanup code as follows:

@After
public void teardown() {
   calculator.clear();
   calculator = null; //this isn't really necessary
}

Looking closely at our code, we can see that we do not need a new BasicCalculator object before executing our test methods. We can re-use one instance of BasicCalculator as long as we invoke clear() on that instance, after the execution of each test method. You might be wondering how to achieve this using JUnit. Actually, it is quite straightforward. JUnit provides an annotation that allows us to signal what methods must be executed prior to running all the test methods. The annotation that makes this possible is @BeforeClass. To be annotated with @BeforeClass, a method must be declared as public as well as static, and it can take no parameters. We no longer need our previous setup() method, so let's tweak it so that it will be executed only once for all the test cases (note that our teardown() method is still invoked after each test method to reset the result of calculator):

private static BasicCalculator calculator;

@BeforeClass
public static void setup() {
  calculator = new BasicCalculator();
}

@After
public void teardown() {
  calculator.clear();
}

Now, let's move on to more challenging arithmetic operations. Let's get down to tackling the test of the multiply() and divide() methods. Luckily for us, testing multiply is straightforward, as shown in the following code snippet:

@Test
public void testMultiply() {
  calculator.add(10);
  calculator.multiply(3);
  assertEquals(30, calculator.getResult());
}

Although testMultiply() looks quite simple, upon running it, we discover a bug. As shown in the following screenshot, instead of returning 30, getResult() returns 9. Note how the error message uses the expected value and the actual value to inform us that our test failed.

Creating test cases

Puzzled, we take a look at the implementation of multiply to discover that rather than multiplying result by the parameter n, we are multiplying n by itself and assigning this value to result. Fortunately, the fix is easy:

public void multiply(int n) {
  result *= n;
}

As you can see, even simple test methods are effective weapons against the bugs that lurk in our code. Nevertheless, so far we just wrote test methods that exercise the happy path of the code of our BasicCalculator. Sometimes we need to write test methods that exercise edge cases of the chunk of code under test. In Java, inputs that cause a certain method to throw an exception can be considered edge cases. For example, an edge case that our calculator has to cope with is division by zero.

In Java, an attempt to divide an integer by zero will lead to an ArithmeticException. If it is expected from our calculator to throw an ArithmeticException whenever there is a division by zero, we need to explicitly state this expectation in our tests.

Fortunately, JUnit provides a way to deal with these edge cases and the associated expected exceptions effectively. The @Test annotation supports two optional parameters. The first, expected, specifies that a test method should throw an exception. If the underlying method throws no exception or the thrown exception is different than the one specified, the test fails.

For instance, we can use this annotation to implement an edge case of our testDivision() method as in the following code. It specifies that the method divide() from BasicCalculator should throw an exception (ArithmeticException) whenever a division by zero takes place. Note that we do not need to use any assert method since the only outcome that we are expecting is an ArithmeticException. It is worth mentioning that the class passed as parameter to expected must be a subclass of java.lang.Throwable.

@Test(expected=ArithmeticException.class)
public void testDivide() {
  calculator.add(10);
  calculator.divide(0);
}

There is yet another way of achieving the same behavior. You can go about declaring expected exceptions through JUnit's ExpectedException rule. First, let us give you a crash course in JUnit rules.

In earlier versions of the test framework, it was not possible to modify or augment JUnit's internal knowledge about a given test suite. Using the current version, we can use rules to modify test results, change a test in a certain way before running it, and add information to test results. We will use the @Rule annotation to inform JUnit which rules to apply to the underlying test.

By using ExpectedException in conjunction with @Rule, you can specify in-test expectations regarding the exceptions that should be thrown for a particular test method. It takes more typing than expected, however, as shown here, you have more control over what is expected. For instance, you can even specify the message what the expected exception should contain.

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void testDivide() {
  thrown.expect(ArithmeticException.class);
  thrown.expectMessage("/ by zero"); //specify the expected message
  calculator.add(10);
  calculator.divide(0);
}

As for the second optional parameter of @Test, timeout, it causes a test method to fail if it takes longer than a specified amount of milliseconds to execute. To give you an example, let's say that we implemented testSquareRoot as an infinite loop, which fails after 1 second. Upon running the code below you should get a red bar along with a java.lang.Exception containing the following message: "test timed out after 1000 milliseconds".

@Test(timeout=1000)
public void testSquareRoot() {
   while(true);
}

Okay, by now, we believe that you got the gist of writing test methods using JUnit and Eclipse. We even were able to uncover a bug in our calculator. Granted, it was a very simple, easy-to-spot bug.

We are sorry to say that not every bug is simple to uncover. Sometimes you might get a red bar that leaves you at a loss to figure out how to fix the problem. In other words, when a test fails, and you just cannot figure out why, even after spending quite some time staring at the code, you need to turn to a debugger. To illustrate this somehow, let's implement the test method, testAddResulToTempAt. The addResultToTemp method should perform the following operation: add the current value of store in the variable result to a position in the tempVars array, according to the index passed as parameter. During the implementation of this test method, we realized that we did not implement a method to get this array of temporary results back. When implementing unit tests, realizing that you forgot something happens more often than not. To set this matter right, we add a simple get method, named getTemps, which simply returns the variable tempVars.

public int[] getTemps() {
  return tempVars;
}

As for our test method, which uses our recently implemented getTemps, we implemented it as the following:

@Test
public void testAddResultToTempAt() {
   calculator.add(9);
   calculator.addResultToTempAt(1);
   int[] temps = calculator.getTemps();
   assertEquals(9, temps[1]);
}

As you can see, we once again use add to store the value 9 into the variable result. Afterwards, in order to store 9 into the slot number 1 of the temporary memory, we invoke addResultToTempAt and pass 1 as parameter. The expected behavior is that the array that represents the calculator's temporary memory now contains the value 9 at index 1. However, the test fails (as shown in the following screenshot). Instead of the expected behavior, we get a NullPointerException. The only clue we got from observing the Failure Trace pane is that the exception is thrown at line number 16 of our BasicCalculator class. Just by looking at this line there is no way you can guess what is wrong (well, it also depends on your Java experience). It is for problems like this that the debuggers exist. So, we will not spoil the fun of discovering the root cause of this problem. Rather, we are going to go over the tools of the trade. That is, we are going to explain how to use Eclipse built-in Java debugger so that you can find and fix this bug by yourself.

Creating test cases
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset