We started by talking about some issues from HW#2.
During HW2, I asked you to consider how you might test that your classes works properly. This seems like a simple question, but it is not always easy. We expect students to test, but we don't often do a good job teaching you how to test.
Today we tried to scratch the surface of this issue.
While we won't spend a huge amount of time on this (really, only today FORMALLY) I hope you don't come away from this thinking that I think that this isn't important. Just the opposite. I suspect that testing is probably one of the most underrated skills and abilities you can develop as a programmer. Just to give you a clue as to how serious a subject this is, our department offers an ENTIRE SEMESTER course on testing (810:175).
When we talk about testing, we are really talking about one of two kinds of error detection.
The first type of error detection is one that most of you are already getting pretty good at. That is, checking for syntax errors. These are easy to catch (although not always easy to fix) because you hit the compile button on your IDE or type the compile command at the console, and the computer tells you if there are problems.
The second type of error detection is the one that I am really interested in today. This is the detection of logic errors. These are errors where the computer "works." But it doesn't work correctly.
In the rest of the discussion today we will talk about how to test for and try to fix this second kind of error.
There are several ways to divide up the types of testing that we do on software systems:
You can also consider:
Positive testing is the testing of cases that are expected to succeed and confirming that you get the answer you expected.
Examples of positive tests from HW #1
Student student1 = new Student("Ben Schafer","123456");
and then invoke
student1.getName();
and confirm that you see "Ben Schafer"
Student student1 = new Student("Ben Schafer","123456");
and then invoke
student1.getName();
and confirm that you see "Ben Schafer"
Negative testing is the testing of cases where you expect your code to 'fail' the situation. In many cases these are the conditions that produce exceptions or error messages. You want to confirm that your code will produce these exceptions or error messages and handle them gracefully.
There is another vocabulary word we should talk about with all of this. That is the concept of boundary testing. Boundary testing is not a third type of testing. Instead, it is a special case of each of the previous two tests. Boundary testing is the idea of testing the "first" positive test case and the "first" negative test case when there are boundaries between several positive cases or between a positive case and a negative case.
For example,
Automated Testing Using JUnit :
One of the techniques for writing good code is to START by considering what the test cases would look like. Then as you write your code you immediately test it with the appropriate cases.
That sounds easy, but what do you do when your program is large? In particular, what should you do when you are working on functionality late in the process but you need to modify some of the earlier functionality. You REALLY need to go back and run all the tests you have run so far to confirm that everything STILL works.
Manual testing as described above can be very time consuming and potentially error prone. Because of that, programmers have turned to techniques of automated testing.
One way to write automated tests is through sometimes elaborate client code that runs through all sorts of situations. To be honest, this is what I normally do, but it can become incredibly complicated.
An alternative is to use a testing process/suite such as JUnit.
JUnit is a third-party package of code that works as a "plug in" of sorts along with your java compiler. While you can configure most modern java IDEs to include JUnit tests, today we will look at a mechanism for running this from the command line:
To do this:
import org.junit.*;
import static org.junit.Assert.*;
public class DiceJUnitTest
{
@Test
public void test_defaultSize()
{
System.out.println("Test if the default size is 6");
Die d1 = new Die();
assertTrue( d1.howManySides()==6 );
}
@Test
public void test_rollInRange()
{
System.out.println("Do rolls produce values in range?");
Die d1 = new Die();
for (int i=0; i<100; i++)
{
int value = d1.roll();
assertTrue( value>0 && value<7 );
}
}
}
************** We stopped here and will finish the rest in class on Monday ******
prompt> javac -cp ".;junit-4.5.jar" DiceJUnitTest.java
prompt> java -cp ".;junit-4.5.jar" org.junit.runner.JUnitCore DiceJUnitTest
Please note that you can run more than one suite of tests at once by including multiple commands
Finally, if you have some "setup" you want to do before each test (sort of like a constructor) you can write something like
private Die d1;
@Before public void setUp()
{
d1= new Die();
while (d1.roll() != 3)
{}
}
For more reading check out:
I showed you the tester I wrote for Student.java from HW#2
I gave you additional instructions for HW#3