Skip to content

Instantly share code, notes, and snippets.

@sturdy5
Created June 23, 2014 18:58
Show Gist options
  • Save sturdy5/8122953c158870b6e3ae to your computer and use it in GitHub Desktop.
Save sturdy5/8122953c158870b6e3ae to your computer and use it in GitHub Desktop.
JUnit Outline

JUnit Overview

Before Reading This

I refer to an IDE in this article that not everyone may be familiar with - RAD. RAD stands for Rational Application Developer and is an IDE provided by IBM. It is pretty much a commercialized version of Eclipse. If you think Eclipse every time you see a reference to RAD, you will be fine. Nothing in this is specific to RAD and I could have substituted "Eclipse" in place of "RAD".

About Unit testing

Unit testing is testing each component of a piece of software as a unit of work. The percentage of code which is covered as part of the test is typically called code coverage. Unit tests help ensure that the code is working as it is supposed to. It also acts as living documentation on how to use the code. Lastly, unit testing provides a protective blanket for refactoring/updating code. Unit tests are inherintly bug repellant. Unit tests should be:

  • a part of the software development life cycle
  • written while coding - before or after is a debate, but you should at least consider how a test will work before you write the code
  • used to test the methods we have written - we know what to expect from a method that we've written, but we have to assert that the method result and expected result match our expectation/requirement.

Test Organization

Unit tests should be in the same project as the code that it is testing. However, it should reside in a different source directory - for example src/test/java instead of src/main/java. The tests themselves should reside in the same java package as the code that it is testing. This will give the JUnit test access to the protected methods within the class under test.

Your unit tests should concentrate the tests on the non-trivial code that you've written. There is no need to test the built in functionality of Java itself, nor the functionality of various third party code. It is best to trust that provided functionality has been tested already.

Inside JUnit

JUnit 3 and 4 are both still used, but we will concentrate on JUnit 4 for this feature list. Equivalents for JUnit 3 are available, but may require more research on how to use them.

Annotations

Annotation Description
@Test identifies a method as a test method
@Test (expected = Exception.class) identifies a method as a test method that is expected to throw an exception of type Exception
@Test (timeout = 100) identifies a method as a test method that should not take longer than 100 milliseconds
@Before identifies a method that should be executed before each test. It is generally used to setup the test environment
@After identifies a method that should be executed after each test. It is generally used to tear down the test environment
@BeforeClass identifies a method that should be executed before any tests are run (not in between tests). This method should be marked static
@AfterClass identifies a method that should be executed after all tests are run (not in between tests). This method should be marked static
@Ignore identifies a test method should be ignored. This should only be used when code is actively in development and should not be committed to source control

Assert Statements

Statement Description
fail(String) Forces a test method to fail
assertTrue(String message, boolean condition) Checks to see if the condition is true. If it fails, the message is printed out
assertFalse(String message, boolean condition) Checks to see if the condition is false. If it fails, the message is printed out
assertEquals(String message, Object expected, Object actual) Checks to see if the actual object value matches the expected object value. NOTE: This will not work for arrays as only the reference is checked, not the values within the array
assertEquals(String message, double expected, double actual, double tolerance) Checks to see if the actual value is within tolerance of the expected value. If it is not, then the message is printed out
assertNull(String message, Object object) Checks to make sure the object is null. If it isn't then the message is printed out
assertNotNull(String message, Object object) Checks to make sure the object is not null. If it is then the message is printed out
assertSame(String message, Object expected, Object actual) Checks to make sure the actual object is the expected object. This is not a check of the value, the objects have to be exactly the same
assertNotSame(String message, Object expected, Object actual) Checks to make sure the actual object is not the expected object. This is not a check of the value, the objects must not be the same object

All of the above assertion statements use String message as the first argument. Technically this argument is optional. In practice it is good form to use the argument to provide meaningful messages for the next developer to identify problems.

In JUnit 4, the assert statements are now included as static imports. Out of the box, RAD does not handle static imports very well. There are two ways to help RAD figure out where the assert methods are:

  1. You can import the org.junit.Assert class and then use the static assert methods on that class - ie. Assert.fail("This should fail")
  2. You can add a content assist for JUnit Asserts. This can be done by opening the Preferences window via Window -> Preferences. Then select Java -> Editor -> Content Assist -> Favorites. Click on the New Type... button to add the org.junit.Assert type. This will help RAD find the assert methods through the Content Assists (Ctrl+Space, etc)

Examples

Suppose we have a Calculator class...

package engine;

/**
 * This class is the sample class under test
 */

public class Calculator {
    public int add(int value1, int value2) {
        return value1 + value2;
    }
    
    public int subtract(int value1, int value2) {
        return value1 - value2;
    }
    
    public int multiply(int value1, int value2) {
        int result = 0;
        for (int i = 0; i < value2; i++) {
            result += value1;
        }
        return result;
    }
    
    public int divide(int value1, int value2) {
        return value1 / value2;
    }
}

To create a new JUnit test within RAD, right-click on your test folder and select New... and then JUnit Test Case. If you haven't already added JUnit to your classpath then RAD will ask you which version and do it for you. When in the wizard to create the test, it will ask you what the class is under test. Selecting that will tell RAD to stub out the test methods with tests that fail.

Here is an example of a JUnit Test Case for the Calculator class...

package engine;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import org.junit.Ignore;
import org.junit.Test;

/**
 * Test class for {@link Calculator}.
 */
public class CalculatorTest {

    /**
     * Test method for {@link engine.Calculator#add(int, int)}.
     */
    @Test
    @Ignore
    public final void testAdd() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link engine.Calculator#subtract(int, int)}.
     */
    @Test
    @Ignore
    public final void testSubtract() {
        fail("Not yet implemented"); // TODO
    }

    /**
     * Test method for {@link engine.Calculator#multiply(int, int)}.
     */
    @Test
    public final void testMultiply() {
        // make sure that 5 * 10 = 50 according to our implementation
        Calculator calc = new Calculator();
        int actual = calc.multiply(5, 10);
        assertEquals("5 * 10 should equal 50", 50, actual);
    }

    /**
     * Test method for {@link engine.Calculator#divide(int, int)}.
     */
    @Test
    @Ignore
    public final void testDivide() {
        fail("Not yet implemented"); // TODO
    }

}

Running this unit test will result in the testMultiply method being executed and the other three being skipped because they have the @Ignore tag on them. All of the tests pass, but Calculator class has a bug in the multiply method.

Unit tests can be used to replicate the problem and resolve them. Someone from the test team has reported that sometimes the calculations are coming up as 0 when it tries to multiply by negative values. So let's modify our JUnit test a little to see how it fails. Let's update our testMultiply method to handle additional scenarios.

public class CaclulatorTest {
    ...
    
    /**
     * Test method for {@link engine.Calculator#multiply(int, int)}.
     */
    @Test
    public final void testMultiply() {
        // make sure that 5 * 10 = 50 according to our implementation
        Calculator calc = new Calculator();
        int actual = calc.multiply(5, 10);
        assertEquals("5 * 10 should equal 50", 50, actual);
        
        // try multiplying -4 and 5 - it should result in -20
        assertEquals("-4 * 5 should equal -20", -20, calc.multiply(-4, 5));
        
        // try multiplying 5 and -2 - it should result in -10
        assertEquals("5 * -2 should equal -10", -10, calc.multiply(5, -2));
        
        // try multiplying -4 and -3 - it should result in 12
        assertEquals("-4 * -3 should equal 12", 12, calc.multiply(-4, -3));
    }
    
    ...
}

When this is executed, JUnit now fails this test saying 5 * -2 should equal -10 expected: <-10> but was: <0>. We seem to have found the issue reported - when the seconds parameter is negative it is returning 0. We can now fix our implementation in this scenario.

public class Calculator {
    ...
    
    public int multiply(int value1, int value2) {
        int result = 0;
        if (value2 >= 0) {
            // if the second value is positive, then add the values
            for (int i = 0; i < value2; i++) {
                result += value1;
            }
        } else {
            // if the second value is negative, then subtract the values
            for (int i = 0; i > value2; i--) {
                result -= value1;
            }
        }
        return result;
    }
    
    ...
}

Running our JUnit test again shows us that all tests pass - we've fixed the bug and now the code is more resilient for future updates.

Parametered Tests

Although the example above is fairly simple, adding more assert method calls within the test method could get to be extremely repetative. Fortunately, JUnit gives parameterized tests to combat this repetativeness. Take a look at the example below of how the test could change.

package engine;

import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
 * Test class for {@link Calculator}.
 */
@RunWith(Parameterized.class)
public class CalculatorTest {

    private int value1;
    private int value2;
    private int result;
    
    public CalculatorTest(int value1, int value2, int result) {
        this.value1 = value1;
        this.value2 = value2;
        this.result = result;
    }
    
    /**
     * Creates the test data that will be passed into the constructor
     */
    @Parameters
    public static Collection<Object[]> data() {
        Object[][] data = new Object[][] { {5, 10, 50}, {-4, 5, -20}, {5, -2, -10}, {-4, -3, 12} };
        return Arrays.asList(data);
    }

    /**
     * Test method for {@link engine.Calculator#multiply(int, int)}.
     */
    @Test
    public final void testMultiply() {
        Calculator calc = new Calculator();
        assertEquals("The multiplication result didn't match what was expected.", result, calc.multiply(value1, value2));
    }
}

To make the test parameterized, we had to change a few things:

  1. Add the annotation @RunsWith(Parameterized.class) to the class definition
  2. Add private class level attributes to hold the parameters
  3. Add a constructor that took in the number of parameters we wanted to supply
  4. Add a method annotated with @Parameters (data() in this case) to generate and provide the parameters
  5. Updated the testMultiply() method to use the class level attributes

References


See more notes at http://jsturdevant.roughdraft.io/


Written with StackEdit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment