Lab: Experimenting with Unit Testing and Coverage
Getting Ready:
Before going any further, you should:
-
Setup your development environment.
-
Download the following files:
to an appropriate directory/folder. (In most browsers/OSs, the
easiest way to do this is by right-clicking/control-clicking on
each of the links above.)
1. A Review of System Testing:
This part of the lab will review some of what you already know
about
system testing (i.e., testing a complete program/application).
-
Execute
RectangleArea
with inputs 10.5 and 13.2.
-
Assuming the prompts, other messages, and format are correct, is
the output correct?
Yes.
-
Change the format specifier from the correct value of 8.2 to the
incorrect value of 7.2 and re-run the program with the same
inputs.
-
You know that the output is not correct (because the specifier is
incorrect). Is it easy to see that the output is incorrect?
No.
-
In the lab on "Using a Command Shell" you used a testing "trick"
to help make it easier to detect defects of this kind. What was
it?
I used output redirection to capture the output of the program and then used
a file comparison utility (like Meld or Kdiff3) to compare the actual output with the expected output.
-
In the lab on "Using a Command Shell" you also used a testing
"trick" to help make it easier to run system tests without having
to type the inputs each time. What was it?
I used input redirection to send a file to the console.
-
What would you need to type at the command line to use both of the
two "tricks" above with the input file you downloaded named
test01.inp
?
java RectangleCalculator < test01.inp > test01.act
-
Just for practice, do it and then compare the expected output
(in the file named
test01.exp
) and the actual output.
-
Change the format specifier back to 8.2 and make sure the output is
now correct.
-
System testing that is performed by developers and/or professional testers
is called alpha (\(\alpha\)) testing. System testing that is
performed by actual or potential users is called beta (\(\beta\))
testing. Have you just acted as an alpha tester or a beta tester?
An alpha tester.
2. Unit Testing without a Test Harness:
As you know,
unit testing involves the testing of individual
modules (a.k.a., units) in isolation.
This part of the lab will help you understand different aspects of
conducting unit testing without a test harness.
-
What methods in the
Geometry
class are tested
in GeometryDriver.java
and how many unit tests does it
contain?
There are two different tests for the method anglesIn()
.
-
Add two unit tests for the
rectangleArea()
method (one of
which must use the same values as the system test above).
-
What code did you add?
double area, height, width;
JMUConsole.print("Testing rectangleArea()\n");
JMUConsole.print("-----------------------\n");
width = 10.5;
height = 13.2;
area = Geometry.rectangleArea(width, height);
JMUConsole.printf("rectangleArea(%5.2f, %5.2f): %5.2f\n",
width, height, area);
width = 20.0;
height = 15.0;
area = Geometry.rectangleArea(width, height);
JMUConsole.printf("rectangleArea(%5.2f, %5.2f): %5.2f\n",
width, height, area);
-
Run
GeometryDriver
.
-
Is the output correct?
It is, but it took a lot of work to be sure.
-
Suppose you were to work on something else for a week and then
re-run
GeometryDriver
. Without looking at the source code,
would you be able to tell if the output was correct or not?
Why or why not?
I certainly wouldn't. I would have to redo all of the calculations.
-
Modify
GeometryDriver
so that it prints both the
expected value and the actual value for each test.
-
What code did you add?
As an example, for the first test of the
rectangleArea()
method,
I changed the code to:
width = 10.5;
height = 13.2;
area = Geometry.rectangleArea(width, height);
JMUConsole.printf("rectangleArea(%5.2f, %5.2f) Expected: %5.2f\n",
width, height, 138.60);
JMUConsole.printf("rectangleArea(%5.2f, %5.2f) Actual: %5.2f\n",
width, height, area);
-
Suppose
GeometryDriver
contained 100 tests.
Would you want to read through all of the output to see if
there were any failures?
I certainly wouldn't -- I'm lazy!
-
Modify the first test of
anglesIn()
so that
it only generates output when there is a failure.
I added an
int
variable named
expectedAngles
and then changed the code to the following:
sides = 3;
expectedAngles = 3;
angles = Geometry.anglesIn(sides);
if (angles != expectedAngles)
{
JMUConsole.printf("anglesIn(%d) Expected: %d\n", sides, expectedAngles);
JMUConsole.printf("anglesIn(%d) Actual: %d\n", sides, angles);
}
-
What would you need to account for if you did a similar thing for
the tests of
rectangleArea()
? (Hint: Think about
performing arithmetic operations on double
values.)
It is never a good idea to use the relational operators
==
and/or !=
with double
values
because of numerical inaccuracies that can arise when operating
on double
values.
-
Suppose you were to do a similar thing for all of the tests in
GeometryDriver
. From a design perspective (not a testing
perspective), what would be wrong with the GeometryDriver
class?
There would be a lot of duplicate code. That is, there would be similar
if
statements in every test.
-
How can you correct the problem identified in the previous question?
Create a method (or two).
3. Building a Simple Harness for Unit Testing:
This part of the lab will help you build a simple harness for unit testing
and understand the advantages of using such a tool.
-
Create a class named
Test
that contains no methods.
-
What code did you add?
/**
* A simple unit testing harness. Note: The methods in this class
* assume that the JMUConsole has alread been opened.
*
* @author Prof. David Bernstein, James Madison University
* @version 1.0
*/
public class Test {
}
-
Add a method to the
Test
class with the following
signature:
public static void forEqualInt(int expected, int actual, String description)
that prints the description
if the values
of expected
and actual
are not the same.
-
What code did you add?
/**
* Display an alert if the actual value is not equal to the
* expected value.
*
* @param expected The expected value
* @param actual The actual value
* @param description A description of the test
*/
public static void forEqualInt(int expected, int actual, String description) {
if (expected != actual) {
JMUConsole.printf("%s Expected: %d, Actual %d\n",
description, expected, actual);
}
}
-
Change the tests of the
anglesIn()
method in the
GeometryDriver
class so that they use this method.
-
What does the code look like now?
Test.forEqualInt(3, Geometry.anglesIn(3), "anglesIn(3)");
Test.forEqualInt(4, Geometry.anglesIn(4), "anglesIn(4)");
-
Add a method to the
Test
class with the following
signature:
public static void forEqualDouble(double expected, double actual,
double tolerance,
String description)
that prints the description
if the difference
between the double
values
expected
and actual
is not within the
given tolerance. Hint: Use Math.abs()
to find the
absolute value.
-
What code did you add?
/**
* Display an alert if the actual double value and the expected double
* value differ by more than the given tolerance.
*
* @param expected The expected value
* @param actual The actual value
* @param tolerance The tolerance
* @param description A description of the test
*/
public static void forEqualDouble(double expected, double actual,
double tolerance,
String description) {
double difference;
difference = Math.abs(expected - actual);
if (difference > tolerance) {
JMUConsole.printf("%s Expected: %5.2f, Actual %5.2f\n",
description, expected, actual);
}
}
-
Change the tests of the
rectangleArea()
method in the
GeometryDriver
class so that they use this method
(with a tolerance of 0.01).
-
What does your code look like now?
Test.forEqualDouble(138.60, Geometry.rectangleArea(10.5, 13.2),
0.01, "rectangleArea(10.5, 13.2)");
Test.forEqualDouble(300.00, Geometry.rectangleArea(20.0, 15.0),
0.01, "rectangleArea(20.0, 25.0)", );
-
If your code is correct, what output should be generated by
GeometryDriver
None.
-
Execute
GeometryDriver
and make sure this is the case.
-
Modify the
rectangleArea()
method in the
Geometry
class so that it incorrectly calculates the
area as the width times the width.
-
Execute
GeometryDriver
.
-
What output was generated?
rectangleArea(10.5, 13.2) Expected: 138.60, Actual 110.25
rectangleArea(20.0, 25.0) Expected: 300.00, Actual 400.00
-
Fix the error in the
Geometry
class.
-
Do you think you could come back a month from now,
rerun the tests on the
Geometry
class, and understand
the output?
Yes
-
Can the test harness you just wrote be used to test other classes?
Yes, that's the beauty of putting the methods in the Test
class
rather than the GeometryDriver
class.