Introduction to Testing

These notes will introduce you to software testing and how to accomplish it in Java using the JUnit framework. These are older notes using JUnit 4 in case you have problems with JUnit 5.

Testing general intro

Obviously it is important to test software before releasing it, to iron out bugs. Software testing can be done in an informal, ad-hoc way, however the disadvantage of this is that the developer is likely to miss out testing crucial functionality. The robustness of the software can be enhanced by taking a more formal approach to testing, by drawing up a test plan documenting all tests and the expected and actual output, as well as performing a series of unit tests designed to test different parts of the system.

Unit testing

A unit test is designed to test one small part of the system in isolation, such as a method. Unit tests are written to test different outcomes of a method. For example, a Product class (used in shop management software) might have a sell() method which takes the number of items to sell as a parameter. This could have three outcomes:

We would create unit tests to test each of the three possible outcomes and check that the expected behaviour does indeed occur in each case.
public class Product
{
    // ... rest of class omitted
    
    static final int SUCCESS = 0, INSUFFICIENT_QUANTITY = 1, INVALID_REQUESTED_QUANTITY = 2;
    
    public int sell (int amount)
    {
        if(quantity
Unit tests for this sell() method could involve:
    

JUnit - A Unit Testing Framework

In Java, unit testing is made straightforward by the open-source unit testing framework JUnit. This is automatically available as part of Eclipse, so is quite easy to use. There is a good tutorial at Vogella which was partly used to research these notes.

Example

Imagine you have an Item class:

public class Item {
    String name;
    double price;
    int qty;
    
    public Item (String name, double price, int qty)
    {
        this.name=name;
        this.price=price;
        this.qty=qty;
    }
    
    public String getName()
    {
        return name;
    }
    
    public double getPrice()
    {
        return price;
    }
    
    public int getQuantity()
    {
        return qty;
    }
    
    public boolean isInStock()
    {
        return qty>0;
    }
    
    public boolean sell()
    {
        if(isInStock())
        {
            qty--;
            return true;
        }
        return false;
    }
    
    public boolean restock(int qty)
    {
        if(qty>0)
        {        
            this.qty += qty;
            return true;
        }
        return false;
        
    }
    public String toString()
    {
        return name +" " +price +" " +qty;
    }
}

To use JUnit, we have to create a test class with a series of unit tests. Here is an example of a series of unit tests that you would run with JUnit on the Item class above:


import static org.junit.Assert.*;
import org.junit.Test;

public class ItemTestBasic {

    public ItemTestBasic()
    {
        
    }
    
    @Test
    public void testSellOnceInStock()
    {
        Item i = new Item("Mars bar",0.60, 1);
        assertEquals(  true, i.sell() );
    }
    
    @Test
    public void testSellOnceNotInStock()
    {
        Item i = new Item("Mars bar",0.60, 0);
        assertEquals(  false, i.sell() );
    }
        
        
    @Test
    public void testIsInStockNeg() {
        Item i = new Item("mars bar", 0.60, -1);
        assertEquals(  false, i.isInStock() );
    }

    @Test
    public void testIsInStockZero() {
        Item i = new Item("mars bar", 0.60, 0);
        assertEquals(  false, i.isInStock() );
    }
    
    @Test
    public void testIsInStockPositive() {
        Item i = new Item("mars bar", 0.60, 1);
        assertEquals(  true, i.isInStock() );
    }
    
    
    
    @Test
    public void testSellTwiceNotInStock()
    {
        Item i = new Item("Mars bar",0.60, 1);
        i.sell();
        assertEquals(  false, i.sell() );
    }
    

    @Test
    public void testRestock() {
        
        int amount=3, initial=10;
        Item i = new Item("Mars bar", 0.60, initial);
        i.restock(amount);
        assertEquals( initial+amount,i.getQuantity());
    }
}


Looking at this in more detail:

Parameterised Tests

The first example performed three very similar tests: testing that the isInStock() method gives the expected result for the values -1, 0 and 1. Clearly this is rather inefficient. It would be better if we could run a single test with one or more parameters - and luckily we can. The example below has a single test, testIsInStockMultipleValues() and runs it three times, passing the parameters -1, then 0, then 1 to it:

import static org.junit.Assert.*;

import org.junit.Test;

import java.util.Arrays;
import java.util.Collection;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;


@RunWith(Parameterized.class)
public class ParamItemTest {

    int parameter;
    
    
    public ParamItemTest(int parameter)
    {
        this.parameter=parameter;
    }
    
    @Parameters
    public static Collection data()
    {
        Object[][] data = { { -1 } , { 0 } , {1 } } ;
        return Arrays.asList(data);
    }
    
    @Test
    public void testIsInStockMultipleValues()
    {
        Item i = new Item("Mars bar",0.60, parameter);
        assertEquals(  parameter>0, i.isInStock() );
    }
}

Note the following:

Parameterised Tests with Multiple Parameters

The following example is a modification of the previous one, which takes multiple parameters per test run:

import static org.junit.Assert.*;

import org.junit.Test;

import java.util.Arrays;
import java.util.Collection;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;


@RunWith(Parameterized.class)
public class ParamItemTest2 {

    int paramAmount;
    boolean paramExpectedResult;
    
    
    public ParamItemTest2(int paramAmount, boolean paramExpectedResult)
    {
        this.paramAmount=paramAmount;
        this.paramExpectedResult=paramExpectedResult;
    }
    
    @Parameters
    public static Collection data()
    {
        Object[][] testData = { { -1, false } , { 0,false } , {1,true } } ;
        return Arrays.asList(testData);
    }
    
    @Test
    public void testIsInStockMultipleValues()
    {
        Item i = new Item("Mars bar",0.60, paramAmount);
        assertEquals(  paramExpectedResult , i.isInStock() );
    }
}

Note how each element in the testData array is now a two-member array, representing the two parameters that are passed into the test class: the initial amount in stock and the expected result of isInStock() for that initial amount. Each pair of values in the testData array will be passed in turn into the constructor, which now takes two parameters corresponding to these two values. Note how the test method uses these parameters to perform the test: it instantiates an Item object using the initial amount paramAmount, and then checks that isInStock() returns a value of paramExpectedResult.

Test Suites

A common approach in testing is to gather a series of tests into a suite. For example, imagine we had a series of tests for a VendingMachine class (omitted) in addition to our Item tests (the idea being that the VendingMachine contains several Items):

import static org.junit.Assert.*;

import org.junit.Test;


public class VendingMachineTest {

    @Test
    public void testAddItem()
    {
        VendingMachine vm=new VendingMachine();
        vm.addItem(id,new Item("Creme Egg",0.60,101));
        assertTrue(vm.itemIsPresent(101));
        
    }
    
    @Test
    public void testSellOK()
    {
        VendingMachine vm = new VendingMachine();
        vm.addItem(101,new Item("Creme Egg",0.60,101));
        vm.insertMoney(0.60);
        assertTrue(vm.sell(101));
    }
    
    @Test
    public void testSellInsufficientMoney()
    {
        VendingMachine vm = new VendingMachine();
        vm.addItem(101,new Item("Creme Egg",0.60,101));
        vm.insertMoney(0.59);
        assertFalse(vm.sell(101));
    }
}
(These tests are used to test whether an item was successfully added, whether an item can be sold if enough money was inserted, and whether an item is unable to be sold if not enough money was inserted. Note also here the use of assertTrue() and assertFalse() which are shorthand versions of assertEquals() in cases where we know the method will return true or false).

References

Lars Vogel's tutorial on JUnit