James Madison University, Spring 2018 Semester
Lab08: Program testing with JUnit
The first computer bug (see Grace Hopper)
Background
One thing we've learned through decades of software engineering is that developing correct software is difficult! The best way to ensure correctness is to test thoroughly while software is being developed. JUnit is a framework that automates testing by providing a standard format for tests and an easy way to execute them (see JUnit.org). In today's lab, you will design and implement your own JUnit tests.
Collaboration: You are encouraged to work with another student to complete this lab. Each of you should submit your own copy of the code. It's okay if your files are similar or identical, as long as both of your names are present at the top.
Objectives
Create a test class for use with JUnit version 4.
Write JUnit test methods that use assertEquals.
Run JUnit test classes and interpret the results.
Key Terms
- test class
- A class that has the purpose of testing some other class, but it is not part of the final application.
- test method
- A method that has the purpose of testing another method, but it is not part of the final application.
- assertion
- A statement that should always be true. Assertions make claims about the correctness of programs.
Part 1: Using JUnit for Testing
The basic JUnit pattern is fairly straightforward:
- For every class
X
there is a companion class namedXTest
that is responsible for testing the class. - For every method
m
there is a companion methodtestM
that is responsible for testing the method.
Main Class | Test Class |
---|---|
public class BasicMath { public static int add(int x, int y) { int sum; sum = x + y; return sum; } public static int subtract(int x, int y) { int diff; diff = x - y; return diff; } }(Note: Javadoc comments have been omitted for compactness.) |
import static org.junit.Assert.*; import org.junit.Test; public class BasicMathTest { @Test public void testAdd() { int expect; int actual; expect = 15; actual = BasicMath.add(7, 8); assertEquals(expect, actual); } @Test public void testSubtract() { int expect; int actual; expect = 2; actual = BasicMath.subtract(3, 1); assertEquals(expect, actual); } } |
Note in the above example:
- Test methods are void, not static, and have no parameters. You can name them however you like.
-
The @Test lines are annotations that tell JUnit which methods it should call to test your program. You may have other (helper) methods that aren't considered tests. To use @Test, the source file must
import
the org.junit.Test class. - Each test method establishes an expected value and runs the corresponding method to get the actual value.
- In the above example, we expect that 7 + 8 will equal 15. We also expect that 3 - 1 will equal 2. The actual values (15 and 2) should be the result of the methods in BasicMath.
- Each test method uses assertEquals (or other assert methods provided by JUnit) to verify correctness.
-
The assertEquals method belongs to the org.junit.Assert class. The
import static
statement allows you to invoke these methods without specifying the Assert class name. Skim through the documentation to see what methods are available.
Let's see how this process works in jGRASP:
Copy and paste the example code above into BasicMath.java and BasicMathTest.java.
Make sure both files are open and then press the Compile button. You should have no errors.
Press the Run button. (Note that since neither class has a main method, you can't "run" them.)
Make sure you are in the BasicMath class. Press the Test Icon at the top . When the pop up box comes up choose "Use Existing File". Then from the BasicMathTest class push the multicolored Run Test button. You should get a green light indicating that all tests completed successfully.
Now go back and "break" the code by changing the BasicMath.add method: return sum + 1;
Compile the new code, and then press the Run Test button again. What happens when an assertion fails?
When you are finished experimenting with JUnit in jGRASP, fix the BasicMath.add method so that all tests pass again. Then write an appropriate Javadoc comment for your BasicMathTest class, including @author and @version tags.
Part 2: Writing Basic Test Methods
-
Implement the following methods in your BasicMath class:
- public static double multiply(double x, double y)
- public static double divide(double x, double y)
-
Now write the corresponding test methods in BasicMathTest. Use the same pattern as in testAdd and testSubtract: establish an expected result and an actual result, then compare the two with an assertion.
-
Floating point arithmetic (i.e., using
float
ordouble
in Java) is not completely accurate due to limitations in computer hardware. Use the following technique to test your multiply and divide methods:double expect; double actual; final double DELTA = 0.000001; expect = 4.1; actual = BasicMath.divide(12.3, 3.0); assertEquals(expect, actual, DELTA);
-
Run your new tests to validate the new methods. Then induce errors into the new methods, just as you did before with the add method, and run the tests again to see if they are really working.
It's generally not enough to test a method just once. To be sure that the method is correct, we need to test multiple times with multiple values.
-
Add the following assertions to the testAdd method. (Copy and paste the last three lines of testAdd for each test below. For readability, separate each test with a blank line.)
- expect 0 when x = 0 and y = 0
- expect -2 when x = -5 and y = 3
-
Add two more assertions to each of the other three test methods. Design your own expected values for these methods.
-
Write Javadoc comments for each of the test methods. It's okay to be brief if what you are testing is straightforward. For example, you could just say "Tests the BasicMath.add method."
Part 3: A More Complex Example
Add the following code to the end of your BasicMath class, but don't look at the code itself. Your goal is to find the mistakes in calculateTax by writing tests based only on its documentation.
/** * Calculate the tax on the given amount based on the following rules: * If the taxType is 'X' or 'x' (exempt), then tax amount is zero. * If the taxType is 'M' or 'm', then tax is 11% of the amount. * If the taxType is 'F' or 'f', then tax is 2% of the amount. * If the taxType is anything else, then tax is 5% of the amount. * * @param amount the amount of the sale * @param taxType type of items purchased * @return amount of tax */
public static double calculateTax(double amount, char taxType) { double tax; switch (taxType) { case 'X': case 'x': tax = 0.0; case 'M': case 'n': tax = 0.11 * amount; case 'F': case 'f': tax = 0.2 * amount; default: tax = 0.5 * amount; } return tax; }
-
Rather than write over a dozen assertions in a single method, create the following test methods:
- testTypeX should test the tax types 'X' and 'x'
- testTypeM should test the tax types 'M' and 'm'
- testTypeF should test the tax types 'F' and 'f'
- testOther should test "anything else" (like '@')
-
For each of the calculateTax test methods, write at least four assertions. Note that you can implement many of these assertions in one line of code, for example:
assertEquals(0.0, BasicMath.calculateTax(1.99, 'X'), DELTA);
-
Run your tests. If all your test methods are correct, all four of them should fail (because the calculateTax method has several mistakes).
-
OPTIONAL: Can you figure out how to fix calculateTax and get your test methods to pass? You may need to learn about the switch statement in the Java tutorials.
Submit your completed BasicMathTest.java file via Canvas by the end of the day.