CS 149: Introduction to Programming
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

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:

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:

Let's see how this process works in jGRASP:

  1. Copy and paste the example code above into BasicMath.java and BasicMathTest.java.

  2. Make sure both files are open and then press the Compile button. You should have no errors.

  3. Press the Run button. (Note that since neither class has a main method, you can't "run" them.)

  4. Make sure you are in the BasicMath class. Press the Test Icon at the top TestIcon. 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.

  5. Now go back and "break" the code by changing the BasicMath.add method: return sum + 1;

  6. 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

  1. Implement the following methods in your BasicMath class:

    • public static double multiply(double x, double y)
    • public static double divide(double x, double y)
  2. 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.

  3. Floating point arithmetic (i.e., using float or double 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);
    
  4. 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.

  1. 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
  2. Add two more assertions to each of the other three test methods. Design your own expected values for these methods.

  3. 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;
    }
  1. 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 '@')
  2. 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);
  3. Run your tests. If all your test methods are correct, all four of them should fail (because the calculateTax method has several mistakes).

  4. 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.