Writing your own software tests in Java is simple and clean. This guide will show you how to simplify some of the less common tasks as well.
Background info:
For student use, we provide a supplemental library (already available to all assignments on Web-CAT) that enhances basic JUnit features in a number of ways that will make your life easier. There are two steps to using this support: make sure the appropriate jar files are on your classpath, and then use a specific base class for all your test cases (even if you are using JUnit 4 techniques--use the provided base class anyway).
To set up your classpath, download the following 2 jar files:
Place both jars on your project build path in the order listed. If you're using Eclipse and you've already added Eclipse's built-in JUnit library to your project, ensure that these two libraries come before Eclipse's in the library search order--or just remove Eclipse's JUnit library from the build path entirely.
All the test classes you create should then extend the base class
student.TestCase
. This class is itself a custom
subclass of junit.framework.TestCase
, so all normal
JUnit practices still work. The advantages of using this subclass
include:
Built-in methods to provide stdin content to code being tested, as well as to check stdout content produced.
Better messages for some assertions, and better error checking for others.
Ability to use JUnit 4 annotations and programming style, if you prefer that over JUnit 3.
Protections for System.exit()
calls in
code under test (i.e., if your code calls
System.exit()
, this raises an exception in your
test case that you can catch, rather than terminating the
virtual machine).
Extra assertions that support "fuzzy" string matching (ignoring punctionation, capitalization, spacing, etc.).
A specialized subclass, GUITestCase
, provides
full support for testing Swing applications with graphical
interfaces.
Platform-independent line ending normalization for stdout output, so that tests do not need to include platform-specific line terminators.
For full details, see the
Javadoc documentation
for student.TestCase
.
Import the JUnit classes so you can use them in your code.
import student.TestCase; ...
Your class should be named using the JUnit convention with the name of the class you are testing followed by the word Test.
If you class is named Student
, then your
test will be named StudentTest
.
import student.TestCase; public class StudentTest extends TestCase { // code goes here... }
Test cases are defined using methods with names that start
with test...()
.
Typically, your method will be named following the method you are
testing in your source code. The example to the right tests the
setName()
method, so it is called
testSetName()
. For more complicated methods, you
may have many tests (e.g., testFoo1()
,
testFoo2()
, testFoo3()
, etc.).
You have to test your assumptions/expectations of your code using
an assertXXX()
method call. These are provided by
the TestCase
base class.
import student.TestCase; public class StudentTest extends TestCase { private Student aStudent; public void setUp() { // fixture to be used for testing aStudent = new Student("Joe", "888-2993"); } public void testSetName() { aStudent.setName("Manuel"); assertEquals(aStudent.getName(), "Manuel"); } }
The most common assert...()
methods you will use to
express the expected outcomes in a test case are:
Method | Meaning |
---|---|
assertEquals(x, y); |
Assert that two values are equal, using the
equals(Object) method to compare them.
This is the most commonly used assert method in your arsenal. |
assertEquals(x, y, delta); |
Assert that two floating point values are equal, within
some tolerance delta. Because of the inexactness of
floating point representations, it is incorrect
to test that two floating point values are equal using numeric
equality (==). Fortunately, if you try this while using
student.TestCase , you'll get an appropriate
diagnostic error. Instead, always specify a tolerance value:
how close to the expected value does the computed number have
to be? |
assertTrue(condition); |
Assert that a boolean condition is true. |
assertFalse(condition); |
Assert that a boolean condition is false. |
assertNull(x); |
Assert that a reference is null. |
assertNotNull(x); |
Assert that a reference is not null. |
assertSame(x, y); |
Assert that two references refer to the same destination (i.e., two variables refer to the same object). This is the same as performing an == equality comparison between reference variables. |
There are many more assert methods than those listed here, but these are by far the most common.
Import the JUnit classes so you can use them in your code.
import student.TestCase; public class StudentTest extends TestCase { // code goes here... }
Fixtures are objects that are used in your test cases.
Define your fixtures as private fields in your test class (instance variables).
Initialize your fixture in the setUp()
method.
The setUp()
method is called automatically
before each test method is executed, to ensure each test operates on
a clean set of freshly initialized objects.
import student.TestCase; public class StudentTest extends TestCase { private Student aStudent; // fixture to be used for testing public void setUp() { // initialize it here aStudent = new Student("Joe", "888-2993"); } }
The two test cases on the right run succesfully, since they
each start with a freshly generated copy of the
aStudent
object.
... public void testSetName() { assertEquals(aStudent.getName(), "Joe"); aStudent.setName("Manuel"); assertEquals(aStudent.getName(), "Manuel"); } public void testSomethingElse() { // aStudent .. is new here assertEquals(aStudent.getName(), "Joe"); assertEquals(aStudent.getID(), "888-2993"); } }
main()
with Standard OutputConsider this simple piece of code.
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello world!"); } }
main()
like any other methodThemain()
method is just a static method, so
you can call it directly like any other. Don't forget its
argments!
import student.TestCase; public class HelloWorldTest extends TestCase { public void testMain() { // call main()! HelloWorld.main(null); } }
System.out
The student.TestCase
base class provides
for capturing all output sent to System.out
during a test case. The history contents are reset before
every test method automatically.
Note that if you are using assertEquals()
, the
expected output must match exactly, character for character,
including all newlines.
Also note that systemOut()
's history is
automatically normalized to represent newlines as "\n",
regardless of platform, so just use \n in any expected values.
public void testMain() { HelloWorld.main(null); // Can combine these into one line if needed String output = systemOut().getHistory(); assertEquals("Hello world!\n", output); }
The student.TestCase
base class provides
an assert method called assertFuzzyEquals()
that performs more lenient string comparisons. It ignores
all differences in capitalization, punctuation, or extra
spacing, as well as all whitespace at the beginning and ending
of strings.
public void testMain() { HelloWorld.main(null); assertFuzzyEquals("hello world", systemOut().getHistory(); }
main()
with Command Line ArgumentsConsider this simple piece of code.
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, " + args[0] + "!"); } }
main()
You can provide any arguments you want when you call
main()
in a test case.
public void testMain() { HelloWorld.main(new String[] { "Joe" }); assertEquals("Hello, Joe!\n", systemOut().getHistory()); }
main()
When It Calls System.exit()
Consider this simple piece of code.
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, " + args[0] + "!"); System.exit(0); } }
You can provide any arguments you want when you call
main()
in a test case.
public void testMain() { try { HelloWorld.main(new String[] { "Joe" }); } catch (student.testingsupport.ExitCalledException e) { // Confirm exit's return value was zero: assertEquals(0, e.getStatus()); } // Check the System.out contents, too: assertEquals("Hello, Joe!\n", systemOut().getHistory()); }
main()
When It Reads From System.in
Consider this simple piece of code.
import java.util.Scanner; public class HelloWorld { public static void main(String[] args) { Scanner in = new Scanner(System.in); System.out.print("Enter your name: "); String name = in.next(); System.out.println("Hello, " + name + "!"); } }
System.in
You can provide your own contents for System.in
within your test case if you are using
student.TestCase
.
public void testMain() { setSystemIn("Joe\n"); // Don't forget the newline! HelloWorld.main(null); assertEquals("Enter your name: Hello, Joe!\n", systemOut().getHistory()); }
Notice that the assert method call in the example shown above includes the full text, including the prompt message. In fact, the prompt message is followed by a space, because that space is part of the prompt itself, but it isn't followed by a newline (the newline is part of what is typed on stdin, not part of what is "printed" by the program). This can be a bit confusing if you have many back-and-forth prompts and responses in a test case action.
Sometimes, it is better to use contains()
or
endsWith()
, rather than testing the entire
output using assertEquals()
.
public void testMain() { setSystemIn("Joe\n"); HelloWorld.main(null); asserttrue(systemOut().getHistory().contains("Hello, Joe!")); }
JUnit.org has more information on how to use JUnit.
Documentation for the asserts in JUnit: http://www.junit.org/apidocs/org/junit/Assert.html
Testing GUI Programs is a Prezi presentation that gives a good introduction to LIFT and how it is used.
The LIFT website provides more details on LIFT, including links to two sigcse papers, downloads, examples, and discussion forums.