Developing Unit Tests

In the previous lab, you used the JUnit framework to implement several unit tests for one of the methods of the Account class. In that lab the focus was on implementing a set of tests that had already been developed. The goal of today's lab is to practice designing appropriate unit tests.

Getting Ready

Before going any further you should:
  1. Make a directory for this lab.
  2. Download the following files into your lab directory:
  3. Configure JGrasp to use JUnit:
    • Select JUnit->Configure under the JGrasp Tools menu.
    • In the box labeled "JUnit Home" browse to the directory where you saved the JUnit .jar file, and click OK.
    • If all went well a green and red box should now appear in your JGrasp toolbar whenever you open a Java file:

1 Brainstorming Tests

The goal of this portion of the lab is to brainstorm a set of tests for the postCharge method of the Account class. THIS SHOULD BE DONE IN GROUPS OF TWO OR THREE.
  1. The first step is to carefully read the JavaDoc comments of the postCharge method. Don't proceed until you have a clear understanding of how that method is supposed to work.
  2. The next step is to design a set of tests that will ensure that behavior of postCharge conforms to the specification. It isn't possible to guarantee that the behavior is correct through testing. Doing so would require that we test postCharge with every possible input for every possible configuration of an account object. Instead, our goal is to come up with a manageable number of tests that cover all of the meaningfully different ways in which the method might be called.

    Characterizing "meaningfully different" situations takes practice. As an example, consider an account with a charge limit of $50.00. It would be overkill to include tests that post charges of $.00, $.01, $.02, all the way up to $49.99. That would require almost 5000 tests and if the method works correctly for a charge of $1.12 it is likely to work correctly for a charge of $1.13. On the other hand, a charge of $49.99 is meaningfully different from a charge of $50.00, and a charge of -$.01 is different from a charge of $.00. These points where the expected behavior of the method changes are referred to as "boundary cases" and they make good candidates for creating tests.

    Take 10-15 minutes to write down an appropriate set of tests. (Type the descriptions into your worksheet.) In developing your tests, you should take into consideration:

    • Boundary conditions.
    • Erroneous inputs.
    • The different states that an account object may be in: open, closed, 0 charge limit, etc.
    For each test, you should describe the test conditions as well as the correct outcome. Here are two examples to get you started:

    Test Correct Outcome
    A charge of $.01 is posted to an open account with a charge limit of $50.00 and a charge balance of $.00. The new charge balance should be $.01.
    A charge of $-.01 is posted to a newly created account. IllegalArgumentException is raised, and the charge balance remains 0.

2 Implementing Tests

  1. Before you start programming, you may want to show your list of tests to the instructor or a TA.
  2. Once you are confident that you have a reasonable set of tests, implement them using JUnit. If you have access to your solution from the previous lab, you may use that as a starting point. Otherwise, click on the Create JUnit Test Case button to create a skeleton testing file. As you finish each test, run your test suite to make sure that the implementation in Account.java passes the test. To get you started, we have provided implementations of the two tests described above. These tests assume that that the test class has an instance variable named testAccount and that setUp has assigned an appropriate value to that variable.
        /***********************************************************
         * Test a valid charge on an account with balance 0. 
         ***********************************************************/
        @Test
        public void testValidChargeOnZeroBalance() 
        {
            testAccount.setChargeLimit(5000); //$50.00
            testAccount.postCharge(1);        //$00.01
            Assert.assertEquals(1, testAccount.getChargeBalance());
        }
    
    
        /***********************************************************
         *  Test negative charge on a new account. 
         ***********************************************************/
        @Test 
        public void testNegativeChargeNewAccount() 
        {
            try 
            {
                testAccount.postCharge(-1);  //-$.01
            }
            catch (IllegalArgumentException e)
            {
                Assert.assertEquals(0, testAccount.getChargeBalance());
                return;
            }
    
            //If we get here, the appropriate exception was not raised.
            Assert.fail();
        }
    
    
    

    Here is an alternate implementation of the second test that takes advantage of JUnit's ability to check that tested code throws the desired exception. This approach would be preferred if the only important outcome of the running the test is the exception.

        /***********************************************************
         *  Test negative charge on a new account. 
         ***********************************************************/
        // TEST WILL FAIL UNLESS THIS EXCEPTION IS RAISED
        //               |
        //               v
        @Test(expected=IllegalArgumentException.class) 
        public void testNegativeChargeNewAccountAlternate() 
        {
            try 
            {
                testAccount.postCharge(-1);  //-$.01
            }
            catch (IllegalArgumentException e)
            {
                Assert.assertEquals(0, testAccount.getChargeBalance());
                throw e;  //re-throw the exception.
            }
    
        }
    
    
  3. Once you have implemented all of your tests, download the file Account.class to your lab directory, overwriting your existing Account.class file. (Be careful not to re-compile Account.java until you are finished with this exercise. Doing so will overwrite the Account.class file that you are testing.) Re-run your tests. Describe any tests that this implementation fails. Without being able to look at the source, what errors do you think were made by the programmer of this version of the Account.java class?

    Hint: There is more than one error in this implementation of Account.java. If you testing didn't uncover them, spend some time rethinking your tests.