Introduction to JUnit
This lab is intended to present an introduction to the JUnit
framework. By the end of this lab, you should have a basic
understanding of what JUnit is used for, and be able to write some
simple JUnit tests on your own.
Resources
1. What is JUnit?
JUnit is a unit testing framework for the Java programming
language. Units are the smallest module of functionality in a computer
program. These are usually in the form of a method. Therefore, JUnit is
most commonly used to test the functionality of individual methods.
Experience with JUnit has been important in the development of
Test-Driven Development, which will be introduced in Lab 2.
2. JUnit 4 Syntax
This section will guide you through JUnit 4 syntax. A Java class holds source-code methods. Similarly, A JUnit test case
holds test-code in unit tests. A JUnit test case is usually associated
with a single Java class. It is generally good practice to have a JUnit
test case for every Java class. Within that JUnit test case, one or
more JUnit unit tests should be written for every source-code method.
2.1. Imports
In JUnit 4.4, three packages should be imported:
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
The static Assert class contains all the JUnit asserts to use.
The Before class is used for running a method to initialize variables
before every unit test, and the Test class is for creating unit tests.
2.2. Test Case
The declaration of a JUnit test case is exactly
the same as a normal Java class. The traditional naming convention for
a JUnit test case is to append the word 'Test' to the name of the Java
class being tested. For example, if we were testing the class Foo, then
the corresponding JUnit test case would traditionally be named FooTest.
2.3. @Before
The @Before annotation is used for a method usually named setUp(). There is only one of these methods per JUnit test case. This method is run once before every unit test (see next section).
2.4. @Test
The @Test annotation signals that the following method is a JUnit unit test. Each @Test method should test the functional correctness of one
method in the corresponding Java class. The traditional method naming
convention is the word 'test' followed by the name of the method being
tested. For example, if we were writing a unit test for a method named fooBar() then our unit test would be named testFooBar().
Let's now look at a concrete example.
3. Example: Unit Tests for a Java Class
We first need some Java classes to test. The following AddSubtract class has two methods; one to add (line 14) and another to subtract (line 21). Similarly, the DivideMultiply class has two methods which let you divide (line 21) or multiply (line 14) a given number with the class variable.
1 public class AddSubtract {
2 private int currentVal;
3
4 /**
5 * Constructor to initialize our member variable.
6 */
7 public AddSubtract(int a) {
8 currentVal = a;
9 }
10
11 /**
12 * Add the value of 'a' to our current value.
13 */
14 public void add(int a) {
15 currentVal += a;
16 }
17
18 /**
19 * Subtract the value of 'a' from our current value.
20 */
21 public void subtract(int a) {
22 currentVal -= a;
23 }
24
25 /**
26 * Get the current value.
27 */
28 public int getCurrentVal() {
29 return currentVal;
30 }
31 }
--------------------------------------------------------------------------
1 public class DivideMultiply {
2 private double currentVal;
3
4 /**
5 * Constructor to initialize our member variable.
6 */
7 public DivideMultiply(double a) {
8 currentVal = a;
9 }
10
11 /**
12 * Multiply 'a' with our current value.
13 */
14 public void multiply(double a) {
15 currentVal *= a;
16 }
17
18 /**
19 * Divide 'a' from our current value.
20 */
21 public void divide(double a) {
22 if (a == 0.0) {
23 throw new java.lang.ArithmeticException("Can't divide by zero!");
24 }
25
26 currentVal /= a;
27 }
28
29 /**
30 * Get the current value.
31 */
32 public double getCurrentVal() {
33 return currentVal;
34 }
35 }
Let's now look at the corresponding JUnit test cases for the previous two Java classes.
1 import static org.junit.Assert.*;
2 import org.junit.Before;
3 import org.junit.Test;
4
5 public class AddSubtractTest {
6 private AddSubtract as; // An instance of the class under test
7
8 /**
9 * This '@Before' method is ran before every '@Test' method
10 */
11 @Before
12 public void setUp() throws Exception {
13 as = new AddSubtract(4);
14 }
15
16 /**
17 * Test method for add()
18 */
19 @Test
20 public void testAdd() {
21 as.add(5);
22 assertEquals(9, as.getCurrentVal());
23 as.add(3);
24 assertEquals(12, as.getCurrentVal());
25 }
26
27 /**
28 * Test method for subtract()
29 */
30 @Test
31 public void testSubtract() {
32 as.subtract(5);
33 assertEquals(-1, as.getCurrentVal());
34 as.subtract(-3);
35 assertEquals(2, as.getCurrentVal());
36 }
37
38 /**
39 * Test method for getCurrentVal()
40 */
41 @Test
42 public void testGetCurrentVal() {
43 assertEquals(4, as.getCurrentVal());
44 }
45 }
--------------------------------------------------------------------------
1 import static org.junit.Assert.*;
2 import org.junit.Before;
3 import org.junit.Test;
4
5 public class DivideMultiplyTest {
6 private DivideMultiply dm; // An instance of the class under test
7
8 /**
9 * This '@Before' method is ran before every '@Test' method
10 */
11 @Before
12 public void setUp() throws Exception {
13 dm = new DivideMultiply(3.0);
14 }
15
16 /**
17 * Test method for multiply()
18 */
19 @Test
20 public void testMultiply() {
21 dm.multiply(2.0);
22 assertEquals(6.0, dm.getCurrentVal(), 0.0000001);
23 }
24
25 /**
26 * Test method for divide()
27 */
28 @Test
29 public void testDivide() {
30 dm.divide(2.0);
31 assertEquals(1.5, dm.getCurrentVal(), 0.0000001);
32 }
33
34 /**
35 * Test method for the exception that divide() throws
36 */
37 @Test(expected=java.lang.ArithmeticException.class)
38 public void testDivideByZero() {
39 dm.divide(0.0);
40 }
41
42 /**
43 * Test method for getCurrentVal()
44 */
45 @Test
46 public void testGetCurrentVal() {
47 assertEquals(3.0, as.getCurrentVal(), 0.0000001);
48 }
49 }
Notice our @Before method in both our test cases initializes the AddSubtract and DivideMultiply classes which we want to test. Since the setUp() method (line 12 in both test-cases) runs before every unit test, it reinitializes our object for every test. So how do we write a test? We use assert statements, some of which are overviewed in the following section.
3.1. Assert statements
JUnit provides a number of assert statements (full list found here under the Assert class). In both our JUnit test cases (AddSubtractTest and DivideMultiplyTest), we use the assertEquals() method. In our testAdd(), line 20, and testSubtract(), line 21, methods within the AddSubtractTest class, the assertEquals() method takes two parameters. The first parameter is the value we expect the method will return. The second parameter is what the actual value is. Let's look at the following assert statement:
assertEquals(6, 3*2);
In this assert, we expect the result of 3*2 to be 6, so we put that as
the first parameter. The second parameter is then the result of what
3*2 is.
In our second JUnit class DivideMultiplyTest, testMultiply(), line 20, and testDivide(), line 29, both use a three parameter assertEquals()
method. The first two parameters are the same as the two parameter
version, the expected result and actual result, respectively. The third
parameter is a delta value for comparing doubles and floats with
fractional values.
3.2. Exception handling
If a method is supposed to throw an exception due to bad data, we can test for that very easily. In our divide() method in the DivideMultiply Java class, it throws an ArithmeticException if the denominator is zero, since we can't divide by zero. We can write a test where we expect
an exception to be thrown. To do this, we put the class file of the
expected exception in parentheses right after the Test annotation. The
format is to have the word expected followed by an equals (=) then the Exception.class file all in parentheses after the @Test annotation (see line 37 of DivideMultiplyTest).
4. Exercise: A Banking account
For this exercise, you will create two files: Account.java and AccountTest.java.
You will have to develop the source-code and test-code on your own
computer, and Web-CAT will be used to submit the exercise for grading.
If you use Eclipse, JUnit comes already installed - ask your instructor
if you have trouble setting it up. If you use JCreator or JGrasp,
instructors for integrating are found here for JCreator and here
for JGrasp. If you use any other IDE, contact your instructor to figure
out how to set up JUnit with your IDE. The objective of this assignment
is to write a class to demonstrate bank account functionality, but then
create a corresponding JUnit test case to ensure its correctness. The Account.java class has to have the following methods and member variables.
- A double member variable to hold the current account balance.
- public Account() {...} The default constructor should initialize the balance to 0.0.
- public void deposit(double amount) {...} A deposit method to add money to the account.
- public double withdraw(double amount) {...} A
withdraw method that withdraws the given amount from the account. If
the amount given can be withdrawn, it should return that amount. If
not, it should return 0.0.
- public double getBalance() {...} A method to get the current balance in the account.
4.1. Submission Process
To hand in your files, click on the Submit
tab in Web-CAT. Follow the instructions to upload your files. Remember
that you will need to create a *.zip file with your two Java files (Account.java and AccountTest.java) in it. Web-CAT will automatically extract your two files from the zip archive.
Chetan Desai - cdesai@calpoly.edu