Classified by how much of a system is being tested
Classified by what is being tested
Classified by testing technique
Also see Wikipedia's software testing category page.
Unit testing is crucial. It is done by developers, not QA. In many organizations, unit tests are required: programmers are not allowed to check-in code or build a system unless their unit tests pass.
Links:
Sometimes one wonders if the world is divided into (1) practitioners that know how to unit test and (2) textbook writers that don't know squat about it.
Some textbook writers (I won't name them here) show you pathetic "test drivers" that go through a class's methods and call System.out.println() a lot.
Like someone's gonna sit there are run the program a gazillion times during debugging and read the output off the screen?!?
Unit tests should be non-interactive. They are made up of a huge number of statements that exercise some code and compare the computed result with the expected result. (Duh! See: the computer checks the result, not a slow dim-witted human.) A test driver can report the number of successes and number of failures when it's finished.
Let's test this money bag class
import java.math.BigDecimal;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
/**
* A little class for holding various amounts of different currencies.
* Example values that a bag can hold are:
* <pre>
* [ ] (nothing)
* [ CHF:1.5 USD:3.0 ] (3 U.S. Dollars and 1.5 Swiss Francs)
* </pre>
* <p>Negative amounts are allowed. Currencies are supposed to be
* legal ISO 4217 codes, but any three-character string made up
* of capital letters in A..Z are allowed.</p>
*/
public class MoneyBag {
private Map<String, BigDecimal> contents =
new TreeMap<String, BigDecimal>();
/**
* Returns how much of a given currency is in the bag.
*/
public BigDecimal getAmount(String currency) {
checkCurrencyCode(currency);
BigDecimal amount = contents.get(currency);
return amount == null ? BigDecimal.ZERO : amount;
}
/**
* Adds (or removes) a bit of a certain currency in the bag.
*/
public void add(BigDecimal amount, String currency) {
checkCurrencyCode(currency);
BigDecimal newAmount = getAmount(currency).add(amount);
if (BigDecimal.ZERO.compareTo(newAmount) == 0) {
// Remove the entry for the currency if the amount goes to zero.
contents.remove(currency);
} else {
contents.put(currency, newAmount);
}
}
/**
* Returns the number of distinct currency types with non-zero
* amounts.
*/
public int numberOfCurrencies() {
return contents.size();
}
/**
* Returns a textual description of the bag in the form
* "[ c1:amount1 c2:amount2 ... cn:amountn ]". The currencies
* are given in alphabetical order.
*/
public String toString() {
StringBuffer buffer = new StringBuffer("[ ");
for (Entry<String, BigDecimal> entry: contents.entrySet()) {
buffer.append(entry.getKey() + ":" + entry.getValue() + " ");
}
buffer.append("]");
return buffer.toString();
}
/**
* Throws an exception if the given currency code is not a three
* letter string consisting solely of the letters A-Z.
*/
private void checkCurrencyCode(String currency) {
if (currency == null || !currency.matches("[A-Z]{3}")) {
throw new IllegalArgumentException("Currency code must be "
+ "three characters long and contain A-Z only");
}
}
}
Here are some important tests for the money bag example
Is that enough? I don't think so. We can go on and on.
It is possible, but not recommended, to write your own tester from scratch:
import java.math.BigDecimal;
/**
* A unit test for the MoneyBag class, written from scratch. Real Java
* programmers use JUnit, so this class is only good for illustrating
* the kinds of things you would consider if you had to write a testing
* framework yourself (which for Java would be never).
*
* <p>This tester is pretty lousy because it doesn't tell you which
* tests failed and why. Besides, since we are counting tests and failures
* manually, other classes that need testing can't share this code. This
* is why we need frameworks like JUnit.</p>
*/
public class MoneyBagTester {
private static int tests = 0;
private static int failures = 0;
private static void expect(boolean condition) {
tests++;
if (!condition) failures++;
}
// Utility class - do not instantiate
private MoneyBagTester() {
}
public static void main(String[] args) {
MoneyBag bag = new MoneyBag();
expect(bag.numberOfCurrencies() == 0);
expect(bag.toString().equals("[ ]"));
bag.add(BigDecimal.ZERO, "USD");
expect(bag.numberOfCurrencies() == 0);
expect(bag.getAmount("CHF").equals(BigDecimal.ZERO));
expect(bag.getAmount("CAD").equals(BigDecimal.ZERO));
expect(bag.getAmount("EEK").equals(BigDecimal.ZERO));
bag.add(BigDecimal.ONE, "USD");
expect(bag.numberOfCurrencies() == 1);
expect(bag.getAmount("USD").equals(BigDecimal.ONE));
bag.add(new BigDecimal("1.50"), "USD");
expect(bag.numberOfCurrencies() == 1);
expect(bag.getAmount("USD").equals(new BigDecimal("2.50")));
bag.add(new BigDecimal("200.0"), "COP");
expect(bag.numberOfCurrencies() == 2);
expect(bag.toString().equals("[ COP:200.0 USD:2.50 ]"));
bag.add(new BigDecimal("-2.50"), "USD");
expect(bag.numberOfCurrencies() == 1);
expect(bag.toString().equals("[ COP:200.0 ]"));
try {bag.getAmount("abc"); expect(false);}
catch (IllegalArgumentException e) {expect(true);}
try {bag.getAmount(null); expect(false);}
catch (IllegalArgumentException e) {expect(true);}
try {bag.getAmount("AB"); expect(false);}
catch (IllegalArgumentException e) {expect(true);}
try {bag.add(BigDecimal.TEN, "ABCD"); expect(false);}
catch (IllegalArgumentException e) {expect(true);}
System.out.println(tests + " test(s), " + failures + " failure(s)");
}
}
When you start to have to do a lot of testing, it helps to have a framework to do a lot of the work for you. JUnit is the framework practically every Java programmer uses.
JUnit, developed by Kent Beck and Erich Gamma, is almost indisputably the single most important third-party Java library ever developed. As Martin Fowler has said, "Never in the field of software development was so much owed by so many to so few lines of code." JUnit kick-started and then fueled the testing explosion. Thanks to JUnit, Java code tends to be far more robust, reliable, and bug free than code has ever been before. — Eliotte Harold
Here is a test for the MoneyBag class
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import org.junit.Before;
import org.junit.Test;
/**
* JUnit test for the money bag class.
*/
public class MoneyBagTest {
private MoneyBag bag;
/**
* All tests should begin with a fresh bag.
*/
@Before
public void initializeBag() {
bag = new MoneyBag();
}
/**
* Tests that a bag is empty upon creation, and stays empty if
* you add a zero amount of some currency, and that retrieving
* currencies that don't exist actually give a zero amount instead
* of throwing an exception or otherwise crashing.
*/
@Test
public void testEmptyBag() {
assertEquals(bag.numberOfCurrencies(), 0);
assertEquals(bag.toString(), "[ ]");
bag.add(BigDecimal.ZERO, "USD");
assertEquals(bag.numberOfCurrencies(), 0);
assertEquals(bag.getAmount("CHF"), BigDecimal.ZERO);
assertEquals(bag.getAmount("CAD"), BigDecimal.ZERO);
assertEquals(bag.getAmount("EEK"), BigDecimal.ZERO);
}
/**
* Tests various methods on non-empty bags.
*/
@Test
public void testNonEmptyBag() {
bag.add(BigDecimal.ONE, "USD");
assertEquals(bag.numberOfCurrencies(), 1);
assertEquals(bag.getAmount("USD"), BigDecimal.ONE);
bag.add(new BigDecimal("1.50"), "USD");
assertEquals(bag.numberOfCurrencies(), 1);
assertEquals(bag.getAmount("USD"), new BigDecimal("2.50"));
bag.add(new BigDecimal("200.0"), "COP");
assertEquals(bag.numberOfCurrencies(), 2);
assertEquals(bag.toString(), "[ COP:200.0 USD:2.50 ]");
assertEquals(bag.getAmount("EEK"), BigDecimal.ZERO);
}
/**
* Tests that making a currency value go to 0 removes the currency
* from the bag.
*/
@Test
public void testCancellation() {
bag.add(BigDecimal.ONE, "USD");
bag.add(new BigDecimal("1.50"), "USD");
bag.add(new BigDecimal("200.0"), "COP");
assertEquals(bag.numberOfCurrencies(), 2);
bag.add(new BigDecimal("-2.50"), "USD");
assertEquals(bag.numberOfCurrencies(), 1);
assertEquals(bag.toString(), "[ COP:200.0 ]");
}
/**
* Tests that uses of illegal currency names throw the right
* exception.
*/
@Test(expected=IllegalArgumentException.class)
public void testTheeCharactersButNotAllUpperCase() {
bag.getAmount("aBC");
}
@Test(expected=IllegalArgumentException.class)
public void testNullCurrencyCode() {
bag.getAmount(null);
}
@Test(expected=IllegalArgumentException.class)
public void testLessThanThreeCharacters() {
bag.getAmount("AB");
}
@Test(expected=IllegalArgumentException.class)
public void testMoreThanThreeCharacters() {
bag.getAmount("CCDE");
}
}
If you are developing with an IDE like Eclipse, you can select "Run as JUnit test case" from a menu, and voila! (Yes, every decent IDE already knows about JUnit and supports it out of the box — you'll mos def find the file junit.jar or something similarly named in your IDE distro.)

You could also run the test case directly from the command line:
$ javac -cp .:junit-4.1.jar MoneyBagTest.java $ java -cp .:junit-4.1.jar org.junit.runner.JUnitCore MoneyBagTest JUnit version 4.1 ....... Time: 0.125 OK (7 tests)
In a large application you might have hundreds or thousands of test files, and you might launch all of them from an ant script. Something like this:
<project name="...">
...
<target name="test" depends="compile-tests"
description="Runs all unit tests">
<junit printSummary="true"
haltOnFailure="true"
fork="true">
<batchtest todir="${test.output.todir}">
<fileset dir="${test.src.dir}">
<include name="**/*Test.java" />
</fileset>
</batchtest>
</junit>
</target>
...
</project>