Testing Grades


The following test, which you should add to StudentTest, demonstrates a range of possibilities. It starts with the simplest case: The student has received no grades, in which case the student's GPA should be 0. It also tests grade combinations based on applying each of the possible letter grades from A through F.

 private static final double GRADE_TOLERANCE = 0.05; ... public void testCalculateGpa() {    Student student = new Student("a");    assertEquals(0.0, student.getGpa(), GRADE_TOLERANCE);    student.addGrade("A");    assertEquals(4.0, student.getGpa(), GRADE_TOLERANCE);    student.addGrade("B");    assertEquals(3.5, student.getGpa(), GRADE_TOLERANCE);    student.addGrade("C");    assertEquals(3.0, student.getGpa(), GRADE_TOLERANCE);    student.addGrade("D");    assertEquals(2.5, student.getGpa(), GRADE_TOLERANCE);    student.addGrade("F");    assertEquals(2.0, student.getGpa(), GRADE_TOLERANCE); } 

The assertEquals method calls in this test show three parameters instead of two. Since floating-point numbers are not precise representations of real numbers, there is a possibility that a calculated value may be off from an expected value by a certain amount. For example, were it possible to code:

 assertEquals(0.9, 3 * 0.3); 

the test would fail:

 AssertionFailedError: expected:<0.9> but was:<0.8999999999999999> 

JUnit provides a third parameter when you need to compare two floating-point values for equality. This parameter represents a tolerance: How much can the two floating-point values be off by before JUnit reports an error?

A general rule of thumb is that values should be off by no more than half of the smallest precision you are interested in representing accurately. For example, if you are working with cents (hundredths of a dollar), then you want to ensure that compared amounts are off by no more than half a cent.

You want to accurately express GPAs to tenths of a point; therefore your tolerance should be 5/100 of a point. The above code defines a static constant GRADE_TOLERANCE local to the StudentTest class and uses it as the third parameter to each assertEquals method:

 assertEquals(2.0, student.getGpa(), GRADE_TOLERANCE); 

In order to make the test pass, you will need to change the Student class to store each added grade in an ArrayList.[2] You can then code the getGpa method to calculate the result GPA. To do so, you must first iterate through the list of grades and obtain a grade point total. You then divide this grade point total by the total number of grades to get the GPA.

[2] You could also calculate the GPA as each grade is added.

 import java.util.*; ... class Student {    private ArrayList<String> grades = new ArrayList<String>();    ...    void addGrade(String grade) {       grades.add(grade);    }    ...    double getGpa() {       if (grades.isEmpty())          return 0.0;       double total = 0.0;       for (String grade: grades) {          if (grade.equals("A")) {             total += 4;          }          else {             if (grade.equals("B")) {                total += 3;             }             else             {                if (grade.equals("C")) {                   total += 2;                }                else {                   if (grade.equals("D")) {                      total += 1;                   }                }             }          }       }       return total / grades.size();    } } 

The method getGpa starts with an if statement to determine whether or not the list of grades is empty. If there are no grades, the method immediately returns a GPA of 0.0. An if statement that appears at the beginning of a method and returns upon a special condition is known as a guard clause. The guard clause in getGpa guards the remainder of the method from the special case where there are no grades. Since you calculate a GPA by dividing by the number of grades, the guard clause eliminates any possibility of dividing by zero.

The method code then uses a for-each loop to extract each grade from the grades collection. The body of the for-each loop compares each grade against the possible letter grades. The body code uses an extension of the if statement known as the if-else statement.

A paraphrasing of the body of the for-each loop: If the grade is an A, add 4 to the total,[3] otherwise, execute the block of code (or single statement) appearing after the else keyword. In this case, the block of code is itself comprised of another if-else statement: If the grade is a B, add 3 to the total, otherwise execute yet another block of code. This pattern continues until all possibilities are exhausted (an F is ignored, since it adds nothing to the grade total).

[3] You may add int values to doubles with no ill effects. However, mixing int and double values in an expression can cause unintended effects. See Lesson 10 for a discussion of numerics in Java.

Complex if-else statements can be difficult to read because of the many curly braces and repeated indenting. In the circumstance where you have repetitive nested if-else statements, you can use a tighter formatting style. Each else-if statement combination goes on the same line. Also, you can eliminate the braces in this example, since the body of each if statement is a single line.

 double getGpa() {    if (grades.isEmpty())       return 0.0;    double total = 0.0;    for (String grade: grades) {       if (grade.equals("A"))          total += 4;       else if (grade.equals("B"))          total += 3;       else if (grade.equals("C"))          total += 2;       else if (grade.equals("D"))          total += 1;    }    return total / grades.size(); } 

This alternative formatting style makes the getGpa method far easier to read but produces the same results.



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net