Coding Bootcamp: Unit Testing with JUnit

Learning objectives

Motivating example

package main.java;

public class MySimpleMath {
    /**
     * A simple method that takes and input and returns
     * "positive" or "negative" depending on the input number 
     */
    public String checkSign(int number) {
        if(number >= 0 ) {
            return "positive";
        } else {
            return "negative";
        }
    }
    
}

Motivating example (2)

    public static void main(String args[]) {
        MySimpleMath sm = new MySimpleMath();
        // Check a positive
        System.out.println("Input 10: " + sm.checkSign(10));
        // Check a negative
        System.out.println("Input -2: " + sm.checkSign(-2));
        // Check a positive
        System.out.println("Input 0: " + sm.checkSign(0));
        
    }

The output of the execution should be

    Input 10: positive
    Input -2: negative
    Input 0: positive

Motivating example (3)

Motivating example (4)

Testing definition

Dynamic verification that the program has the expected behavior in a suitably selected final set of test cases

Unit testing definition

Unit testing is a method of testing individual units of source code to determine if they are fit for use

Test Class



Test class (2)

Test class (3)

Test classes should follow the same package structure with the functional classes

Proper testing of the "simple" example


package test.main;

import org.junit.*;
import main.java.MySimpleMath;

public class MySimpleMathTest {
    
    @Test
    public void testCheckSignShouldReturnPositive() {
        MySimpleMath sm = new MySimpleMath();
        Assert.assertEquals("positive", sm.checkSign(5));
        Assert.assertEquals("positive", sm.checkSign(0));
    }
    
    @Test
    public void testCheckSignShouldReturnNegative() {
        MySimpleMath sm = new MySimpleMath();
        Assert.assertEquals("negative", sm.checkSign(-5));
    }
    
}

Executing the test cases



JUnit good practices

Test cases annotations @

Assert

assertTrue(“message”,boolean condition)
assertFalse(…)
assertEquals(“message”, expected, actual, tolerance)
assertNotEquals(unexpected, actual);
assertNull(“message”, object)
assertNotNull(…)
assertSame(“message”, expected, actual)
assertNotSame(…)
assertArrayEquals(…)
assertArrayNotEquals(…)
fail()

Testing exceptions

package main.java;

public class MySimpleMath {
    
    /**
     * A simple method that takes and input and returns
     * "positive" or "negative" depending on the input number 
     */
    public String checkSign(int number) {
        if(number >= 0 ) {
            return "positive";
        } else {
            return "negative";
        }
    }

    /**
     * Returns the division of numerator by the denominator.
     * If the denominator is zero, it throws an Exception
     */
    public double divide(int num, int denom) {
        if(denom == 0) { 
            throw new ArithmeticException("Cannot divide by zero");
        } else {
            return num/(double)denom;
        }
        
    }
    
}

Testing exceptions (2)

package test.main;

import org.junit.*;
import main.java.MySimpleMath;

public class MySimpleMathTest {
    
    @Test
    public void testCheckSignShouldReturnPositive() {
        MySimpleMath sm = new MySimpleMath();
        Assert.assertEquals("positive", sm.checkSign(5));
        Assert.assertEquals("positive", sm.checkSign(0));
    }
    
    @Test
    public void testCheckSignShouldReturnNegative() {
        MySimpleMath sm = new MySimpleMath();
        Assert.assertEquals("negative", sm.checkSign(-5));
    }
    
    @Test
    public void testDivisionShouldReturnPositiveQuotient() {
        MySimpleMath sm = new MySimpleMath();
        Assert.assertEquals(2.0, sm.divide(10, 5), 0);
        Assert.assertEquals(0.0, sm.divide(0, 5), 0);
    }
    
    @Test
    public void testDivisionShouldReturnNegativeQuotient() {
        MySimpleMath sm = new MySimpleMath();
        Assert.assertEquals(-2.0, sm.divide(10, -5), 0);
    }
    
    @Test (expected = ArithmeticException.class)
    public void testDivisionShouldThrowArithmeticException() {
        MySimpleMath sm = new MySimpleMath();
        sm.divide(10, 0);
    }
    
}

Testing Arrays

package main.java;

public class MySimpleArrayOperations {
    
    public int findMin(int[] array) {
        if(!(array.length > 0)) {
            throw new IllegalArgumentException("Input array is empty");
        }
        
        int min = Integer.MAX_VALUE;
        for(int i=0; i<array.length; i++) {
            if(array[i] <= min)
                min = array[i];
        }
        
        return min;
    }
    
    public void multiply(int[] array, int factor) {
        if(!(array.length > 0)) {
            throw new IllegalArgumentException("Input array is empty");
        }
        
        for( int i=0; i<array.length; i++ ) {
            array[i] = array[i] * factor;
        }
    }

}

Testing Arrays (2)

package test.main;

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

import main.java.MySimpleArrayOperations;

public class MySimpleArrayOperationsTest {
    
    @Test
    public void testFindMin() {
        MySimpleArrayOperations msao = new MySimpleArrayOperations();
        int[] array = {10, 2, 3, 10, 1, 0, 2, 3, 16, 0, 2};
        assertEquals(0, msao.findMin(array));
        assertNotEquals(10, msao.findMin(array));
    }
    
    @Test (expected = IllegalArgumentException.class)
    public void testFindMinShouldThrowException() {
        MySimpleArrayOperations msao = new MySimpleArrayOperations();
        msao.findMin(new int[]{});
    }
    
    @Test
    public void testMultiply() {
        MySimpleArrayOperations msao = new MySimpleArrayOperations();
        int[] array = {10, 2, 3, 10, 1, 0, 2, 3, 16, 0, 2};
        msao.multiply(array, 10);
        assertArrayEquals(new int[]{100, 20, 30, 100, 10, 0, 20, 30, 160, 0, 20}, array);
    }
    
    @Test (expected = IllegalArgumentException.class)
    public void testMultiplyShouldThrowException() {
        MySimpleArrayOperations msao = new MySimpleArrayOperations();
        msao.multiply(new int[]{}, 0); //method call with dummy arguments
    }
    
}

Simplify the test cases

Simplify the test cases (2)

package test.main;

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

import main.java.MySimpleArrayOperations;

public class MySimpleArrayOperationsTest {
    private MySimpleArrayOperations msao = new MySimpleArrayOperations();
    private int[] array;
    
    @Before
    public void initInstanceVariables() {
        System.out.println(this.getClass().getName() + " --> initializing fields");
        this.msao = new MySimpleArrayOperations();
        this.array = new int[] {10, 2, 3, 10, 1, 0, 2, 3, 16, 0, 2};
    }
    
    @Test
    public void testFindMin() {
        assertEquals(0, msao.findMin(array));
        assertNotEquals(10, msao.findMin(array));
    }
    
    @Test (expected = IllegalArgumentException.class)
    public void testFindMinShouldThrowException() {
        msao.findMin(new int[]{});
    }
    
    @Test
    public void testMultiply() {
        msao.multiply(array, 10);
        assertArrayEquals(new int[]{100, 20, 30, 100, 10, 0, 20, 30, 160, 0, 20}, array);
    }
    
    @Test (expected = IllegalArgumentException.class)
    public void testMultiplyShouldThrowException() {
        msao.multiply(new int[]{}, 0); //method call with dummy arguments
    }
    
}

Test suites

package test.java;

import org.junit.runner.RunWith;
import org.junit.runners.Suite;

@RunWith(Suite.class)
@Suite.SuiteClasses({ MyMathTest.class, MyMathSimpleTest.class })

public class MathTestSuite {
    /* the class remains empty, used only as a holder for  
     * the above annotations
     */
}

Executing multiple tests

package test.java;

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(MathTestSuite.class, 
              MyArrayOperationsTest.class);
        
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
        
      System.out.println(result.wasSuccessful());
   }
}  

Testing Lists

Testing Lists (2)

import java.util.ArrayList;
import java.util.Arrays;

public class MyIntegerList {
    private ArrayList<Integer> list;
    
    public MyIntegerList(Integer[] array) {
        this.list = new ArrayList<>(Arrays.asList(array));
    }
    
    public Integer[] getListAsAnArray() {
        return (Integer[]) this.list.toArray();
    }
    
    public void add(int n) {
        this.list.add(n);
    }
    
    public void remove() {
        if(!this.list.isEmpty())
            this.list.remove(this.list.size()-1);
    }
    
    public int get(int index) {
        if(this.list.size() - 1 >= index) 
            return this.get(index);
        else 
            return (Integer) null;
    }
    
    public int size() {
        return this.list.size();
    }
    
    public void clear() {
        this.list.clear();
    }
}

Testing Lists (3)

package test.main;

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

import main.java.MyIntegerList;

public class MyIntegerListTest {

    private MyIntegerList myList;
    
    @Before
    public void initInstanceVariables() {
        System.out.println(this.getClass().getName() + "--> initializing array");
        Integer array[] = {1,2,3,4,5};
        this.myList = new MyIntegerList(array);
    }
    
    @Test
    public void testSize() {
        assertEquals(5, this.myList.size());
        assertEquals(0, new MyIntegerList(new Integer[]{}).size());
        this.myList.add(6);
        assertEquals(6, this.myList.size());
    }
    
    @Test
    public void testAdd() {
        this.myList.add(10);
        assertArrayEquals(new Integer[]{1,2,3,4,5,10},this.myList.getListAsAnArray());
    }
    
    // the other test cases are omitted to simplify the example
}

References

Exercise 1

Exercise 1 (continued)

Exercise 1 (continued)

Exercise 2

Exercise 2 (continued)

Exercise 2 (continued)

Exercise 3


Creative Commons Licence
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.