Skip to main content

Testing: Loops

Test All Paths

Once we have possible paths, we can create input values that will test each of the paths (See Unit & Integration Testing). Creating tests to consider all paths of statements is straightforward. Creating tests to consider all paths of loops is more complex. We could create many more paths that would execute the loop more than once, leading to a potentially infinite number of test cases. There is typically not enough time to run all possible test cases, so only focus on the paths through the code where a loop is run once through its body.

Pressman1 provides the following guidance for testing a simple loop (i.e., no nesting), where the loop is expected to iterate n times.

  • Fail the conditional test for entering the loop, so that the loop never executes;
  • Execute the body of the loop only once;
  • Execute the body of the loop twice;
  • Execute the body of the loop m times, where m < n;
  • Execute the body of the loop n – 1 times;
  • Execute the body of the loop n times; and
  • Execute the body of the loop n + 1 times.

A loop’s execution ranges from the lower boundary to n. The first set of three test cases test the loop’s lower boundary value. The 4th test case is a representative equivalence class test of the loop’s input range. The last 3 test cases test loop’s upper boundary value. Some of the tests may lead to redundancies if the loop’s bounds are dependent on the input, so create as many distinct tests as possible, ensuring at a minimum coverage of all paths through the loop.

Nested loops introduce additional complexity when testing. Pressman1 gives the following guidance for testing nested loops:

  • Keeping all outer loops to minimal values that reduce the number of iterations, test the innermost loop using the techniques listed above.
  • Move up the level of nested loops, and test the loop using the techniques listed above. The outer loops should kept to minimal iterations and any inner loops should be iterated a “typical” number of times.
  • Continue moving up the level of nested loops until all loops are tested.

Robust Paycheck Requirements

Raleigh’s Parks and Recreation Department hires landscapers to care for and maintain the city’s parks.

Skill Level

An employee has one of three skill levels; each with a hourly pay rate:

Skill Level Hourly Pay Rate ($)
Level 1 $19.00
Level 2 $22.50
Level 3 $25.75

Deductions

All employees may opt in for insurance, which results in a deduction from their pay check.

Deduction Weekly Cost ($)
Option 1 - Medical Insurance $24.50
Option 2 - Dental Insurance $15.30
Option 3 - Vision Insurance $5.25

Employees at skill level 3 may also opt to place up to 6% of their gross pay into a retirement account.

Input

The Paycheck program prompts the user for information about each Employee, including the name, level (1, 2, or 3), hours worked, retirement percent, and whether he or she has medical, dental, and vision insurances. This version of the program has extensive error checking and loops to allow for processing more than one paycheck at a time.

Output

The following information is printed about each employee’s pay check:

  1. employee’s name
  2. hours worked for a week
  3. hourly pay rate
  4. regular pay for up to 40 hours worked
  5. overtime pay (1.5 pay rate) for hours over 40 worked
  6. gross pay (regular + overtime)
  7. total deductions
  8. net pay (gross pay – total deductions).

If the net pay is negative, meaning the deductions exceeds the gross pay, then an error is printed.

System Testing with Loops

For System Tests for Robust Paycheck, we want to test different number of paychecks:

Test ID Description Expected Results Actual Results
No new paycheck Preconditions: RobustPaycheck program started
New paycheck (Y/N):
N
 
Boundary regular hours,
One insurance,
Level 3,
Boundary retirement
Preconditions: RobustPaycheck program started
New paycheck (Y/N):
Y
Employee Name: Ellen Edwards
Employee Level: 3
Hours Worked: 39
Medical Insurance (Y/N): N
Dental Insurance (Y/N): N
Vision Insurance (Y/N): Y
Retirement Percentage (0-6): 5
New paycheck (Y/N):
N
Name Hours PayRate Regular OT Gross Deduc. Net
Ellen Edwards 39.00 25.75 1004.25 0.00 1004.25 55.46 948.79
 
Multiple new paychecks Preconditions: RobustPaycheck program started
New paycheck (Y/N):
Y
Employee Name: George George
Employee Level: 3
Hours Worked: 41
Medical Insurance (Y/N): N
Dental Insurance (Y/N): Y
Vision Insurance (Y/N): N
Retirement Percentage (0-6): 2
New paycheck (Y/N):
Y
Employee Name: Hilda Henderson
Employee Level: 3
Hours Worked: 50
Medical Insurance (Y/N): N
Dental Insurance (Y/N): N
Vision Insurance (Y/N): N
Retirement Percentage (0-6): 3
New paycheck (Y/N):
N
Name Hours PayRate Regular OT Gross Deduc. Net
George George 41.00 25.75 1030.00 38.62 1068.62 36.67 1031.95
Name Hours PayRate Regular OT Gross Deduc. Net
Hilda Henderson 50.00 25.75 1030.00 386.20 1416.20 42.48 1373.72
 

Unit and Integration Testing with Loops

We will examine testing one method that contains a loop. Full testing can be seen below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    /**
     * Returns the hours worked for a given employee as entered by the user.
     * 
     * @param console Scanner for reading from the console
     * @return hours worked
     */
    public static double getHoursWorked(Scanner console) {
        double hoursWorked = 0;
        while (hoursWorked <= 0) {
            System.out.print("Hours Worked: ");
            if (console.hasNextDouble()) {
                hoursWorked = console.nextDouble();
            }
            clearLine(console);
        }
        return hoursWorked;
    }

Control flow diagram labeled with line numbers:

Control flow diagram templates for getHoursWorked method.
Control flow diagram templates for getHoursWorked method.


Testing all of the paths through getHoursWorked:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    /**
     * Test the RobustPaycheck.getHoursWorked() method.
     */
    @Test
    public void testGetHoursWorked() {
        // Test boundary value
        Scanner input = new Scanner("1\n");
        assertEquals(1, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test boundary value for get hours worked");

        // Test boundary value
        input = new Scanner("0.1\n");
        assertEquals(0.1, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test boundary value for get hours worked");

        // Test mid regular value
        input = new Scanner("20.5\n");
        assertEquals(20.5, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test mid regular value for get hours worked");

        // Test max regular value
        input = new Scanner("40.0\n");
        assertEquals(40, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test max regular value for get hours worked");

        // Test overtime value
        input = new Scanner("60\n");
        assertEquals(60, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test overtime value for get hours worked");

        // Test invalid first value
        input = new Scanner("0\n 60\n");
        assertEquals(60, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test invalid first value for get hours worked");

        // Test invalid first value
        input = new Scanner("-1\n 60\n");
        assertEquals(60, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test invalid first value for get hours worked");

        // Test invalid first value
        input = new Scanner("four\n 60\n");
        assertEquals(60, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test invalid first value for get hours worked");

        // Test invalid first value
        input = new Scanner("number 3\n 60\n");
        assertEquals(60, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test invalid first value for get hours worked");
    }

Timeout Limit

An infinite loop in our source code would result in an infinite loop when running our test program. Within our test program, we can use the assertTimeoutPreemptively method to fail a test if the timeout limit is reached.

The following test will timeout after 5 seconds with the message: “Timeout limit reached”. You will need to add the following import to your test program: import java.time.Duration;

1
2
3
4
5
6
7
8
9
10
11
12
    /**
     * Test the RobustPaycheck.getHoursWorked() method.
     */
    @Test
    public void testGetHoursWorked() {
        // Test invalid first value
        input = new Scanner("number 3\n 60\n");
        assertTimeoutPreemptively(Duration.ofSeconds(5), () -> {
            assertEquals(60, RobustPaycheck.getHoursWorked(input), DELTA,
                "Test invalid first value for get hours worked");
        }, "Timeout limit reached");
    }

Testing Materials for Robust Paycheck

Footnotes

  1. Pressman, R. S. (2005). Software Engineering: A Practitioner’s Approach (6th ed.). McGraw-Hill.  2