Showing posts with label tdd / test driven development. Show all posts
Showing posts with label tdd / test driven development. Show all posts

Monday, 6 June 2011

EasyMock for Unit Tests

What is mock object?
A mock object is a dummy interface or class in which you define the dummy output of a certain method call. These objects can be provided to the class which should be tested to avoid any dependency to external data. The classical example is a mock object for a data provider. In production a real database is used but for testing a mock object simulates the database and ensures that the test conditions are always the same.

What is easy mock?
This basic requirement of creating mock object can be accomplished by using Easy mock. Easy mock is a library that helps to dynamically create mock objects. This way we can avoid much of the tedious work involved in manually coding mock objects. However, EasyMock itself is not the silver bullet, we still have to define expectations in test cases. Using EasyMock also means that you have make certain design changes (if your design follows the usual design principles you may not have to change much. Here's a sample Unit Test using EasyMock. You can download easymock from here.
Class Under Test
DateFormatter.java
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFormatter {
public static final String DATE_FORMAT = "MM/dd/yyyy";

private DateFormatterHelper helper = null;

public DateFormatter() {
}

public String convertToStandardFormat(String dateString, String format)
throws ParseException {
if (dateString == null || dateString.equals(""))
return "";
dateString = dateString == null ? "" : dateString;
DateFormat df = helper.getDateFormat(format);
Date date = df.parse(dateString);
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
return sdf.format(date);
}

public DateFormatterHelper getHelper() {
return helper;
}

public void setHelper(DateFormatterHelper helper) {
this.helper = helper;
}
}

Testing the class

Helper Class and Interface: If you are to mock any class using EasyMock, it is required that you have an interface for the class. If you are following the old design principle "program to an interface, not an implementation", you are good here.

package sample;

import java.text.DateFormat;

public interface DateFormatterHelper {
public DateFormat getDateFormat(String format);
}

DateFormatterHelperImpl.java
public class DateFormatterHelperImpl 
implements DateFormatterHelper {

public DateFormatterHelperImpl() {
}

public DateFormat getDateFormat(String format) {
SimpleDateFormat sdf = new SimpleDateFormat(format);
sdf.setCalendar(Calendar.getInstance());
sdf.setLenient(false);
return sdf;
}


public static void main(String args[]) {
DateFormatterHelper helper = new DateFormatterHelperImpl();
try {
System.out.println(helper.
getDateFormat("MM/dd/yyyy").parse("11/27/2008"));
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}

}

DateFormatterTests.java
import static org.junit.Assert.fail;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import sample.DateFormatter;
import sample.DateFormatterHelper;

public class DateFormatterTests {

private DateFormatter formatter = null;

DateFormatterHelper helper = null;

@Before
public void setUp() throws Exception {
helper = EasyMock.createMock(DateFormatterHelper.class);
formatter = new DateFormatter();
formatter.setHelper(helper);
}

@Test
public void testConvertToStandardFormat() {

String formatted = null;
try {
EasyMock.expect(helper.getDateFormat("MM-dd-yyyy"))
.
andReturn(new SimpleDateFormat("MM-dd-yyyy"));
EasyMock.replay(helper);
formatted = formatter.
convertToStandardFormat("11-27-2008", "MM-dd-yyyy");
} catch (ParseException e) {
e.printStackTrace();
fail("Exception");
}
Assert.assertEquals(formatted, "11/27/2008");

}
}

Note that in the EasyMock.expect method line, we use the "andReturn" method. This is to define the expected behavior when the method under test is invoked by the testcase. As can be expected, the replay method simply replays the pre-defined behavior when the actual call is made on the mock object.

Download the source
You can download the source code from here.

JUnit - Tutorial

Unit testing with JUnit 
A unit test is a piece of code written by a developer that tests a specific functionality in the code which is tested. Unit tests can ensure that functionality is working and can be used to validate that this functionality still works after code changes.

Setup JUnit in eclipse
Here we will see how to setup the test classes in eclipse:
  1. Create a new project, name it whatever you want, say JUnitDemo.
  2. It would be good to create the unit tests in a separate folder as a good practise. Create therefore a new source folder "test" via right mouse click on your project, select properties and choose the "Java Build Path". Select the tab source code. Press "Add folder" then then press "Create new folder". Create the folder "test".
    before junit
  3. Create the class to be tested - Add it to the normal source folder.
    public class Calc { public long add(int a, int b) { return a+b; } }
  4. Tester class - Right click on test folder, and either make a new JUnit test case or a simple class and add @Test annotation on the methods of this class.
    import org.junit.Test; import static org.junit.Assert.assertEquals; public class CalcTest { @Test public void testAdd() { assertEquals(5, new Calc().add(2, 3)); } }
  5. Now run the test class like this:
    JUnit Run as
  6. Now in the end you will see the following result as green if test was successful and red otherwise.
Creating the testee and tester class

  1. The tests: Unlike in JUnit 3.x you don't have to extend TestCase to implement tests. A simple Java class can be used as a TestCase. The test methods have to be simply annotated with org.junit.Test annotation as shown below
    @Test
    public void emptyTest() {
    stack = new Stack<String>();
    assertTrue(stack.isEmpty());
    }
  2. Using Assert Methods: In JUnit 4 test classes do not inherit from TestCase, as a result, the Assert methods are not available to the test classes. In order to use the Assert methods, you have to use either the prefixed syntax (Assert.assertEquals()) or, use a static import for the Assert class.
    import static org.junit.Assert.*;
    Now the assert methods may be used directly as done with the previous versions of JUnit.
  3. Changes in Assert Methods: The new assertEquals methods use Autoboxing, and hence all the assertEquals(primitive, primitive) methods will be tested as assertEquals(Object, Object). This may lead to some interesting results. For example autoboxing will convert all numbers to the Integer class, so an Integer(10) may not be equal to Long(10). This has to be considered when writing tests for arithmetic methods. For example, the following Calc class and it's corresponding test CalcTest will give you an error.
    Calc.java
    public class Calc {
    public long add(int a, int b) {
    return a+b;
    }
    }

    CalcTest.java
    import org.junit.Test;
    import static org.junit.Assert.assertEquals;

    public class CalcTest {
    @Test
    public void testAdd() {
    assertEquals(5, new Calc().add(2, 3));
    }
    }

    You will end up with the following error.
    java.lang.AssertionError: expected:<5> but was:<5>

    This is due to autoboxing. By default all the integers are cast to Integer, but we were expecting long here. Hence the error. In order to overcome this problem, it is better if you type cast the first parameter in the assertEquals to the appropriate return type for the tested method as follows:
    assertEquals((long)5, new Calc().add(2, 3));

    There are also a couple of methods for comparing Arrays:
    public static void assertEquals(String message, Object[] expecteds, Object[] actuals);
    public static void assertEquals(Object[] expecteds, Object[] actuals);
  4. Setup and TearDown: You need not have to create setup and teardown methods for setup and teardown. The @Before, @After and @BeforeClass, @AfterClass annotations are used for implementing setup and teardown operations. The @Before and @BeforeClass methods are run before running the tests. The @After and @AfterClass methods are run after the tests are run. The only difference being that the @Before and @After can be used for multiple methods in a class, but the @BeforeClass and @AfterClass can be used only once per class.
  5. Parameterized Tests: JUnit 4 comes with another special runner: Parameterized, which allows you to run the same test with different data. For example, in the the following peice of code will imply that the tests will run four times, with the parameter "number" changed each time to the value in the array.
    @RunWith(value = Parameterized.class)
    public class StackTest {
    Stack<Integer> stack;
    private int number;

    public StackTest(int number) {
    this.number = number;
    }

    @Parameters
    public static Collection data() {
    Object[][] data = new Object[][] { { 1 }, { 2 }, { 3 }, { 4 } };
    return Arrays.asList(data);
    }
    ...
    }

    The requirement for parameterized tests is to
    • Have the annotation @RunWith for the Test Class
    • Have a public static method that returns a Collection for data. Each element of the collection must be an Array of the various paramters used for the test.
    • You will also need a public constructor that uses the parameters
  6. Test Suites: In JUnit 3.8 you had to add a suite() method to your classes, to run all tests as a suite. With JUnit 4 you use annotations instead. To run the CalculatorTest and SquareTest you write an empty class with @RunWith and @Suite annotations.
    import org.junit.runner.RunWith;
    import org.junit.runners.Suite;
    @RunWith(Suite.class)
    @Suite.SuiteClasses({StackTest.class})
    public class AllTests {
    }

    The "Suite" class takes SuiteClasses as argument which is a list of all the classes that can be run in the suite. The following is a listing of the example StackTest used in the post:

Static imports with Eclipse

JUnit uses a lot of static methods and old Eclipse cannot automatically import static imports, but new one can. You can make the JUnit test methods available via the content assists.
Open the Preferences via Window -> Preferences and select Java > Editor > Content Assist > Favorites. Add then via "New Member" the methods you need. For example this makes the assertTrue, assertFalse and assertEquals method available.

JUnit static imports


You can now use Content Assist (Ctrl+Space) to add the method and the import.
I suggest to add at least the following new members.
  • org.junit.Assert.assertTrue
  • org.junit.Assert.assertFalse
  • org.junit.Assert.assertEquals
  • org.junit.Assert.fail

Annotations

The following give an overview of the available annotations in JUnit 4.x


Annotations
AnnotationDescription
@Test public void method()Annotation @Test identifies that this method is a test method.
@Before public void method()Will perform the method() before each test. This method can prepare the test environment, e.g. read input data, initialize the class)
@After public void method()Test method must start with test
@BeforeClass public void method()Will perform the method before the start of all tests. This can be used to perform time intensive activities for example be used to connect to a database
@AfterClass public void method()Will perform the method after all tests have finished. This can be used to perform clean-up activities for example be used to disconnect to a database
@IgnoreWill ignore the test method, e.g. useful if the underlying code has been changed and the test has not yet been adapted or if the runtime of this test is just to long to be included.
@Test(expected=IllegalArgumentException.class)Tests if the method throws the named exception
@Test(timeout=100)Fails if the method takes longer then 100 milliseconds

Assert statements

The following gives an overview of the available test methods:


Test methods
StatementDescription
fail(String)Let the method fail, might be usable to check that a certain part of the code is not reached.
assertTrue(true);True
assertsEquals([String message], expected, actual)Test if the values are the same. Note: for arrays the reference is checked not the content of the arrays
assertsEquals([String message], expected, actual, tolerance)Usage for float and double; the tolerance are the number of decimals which must be the same
assertNull([message], object)Checks if the object is null
assertNotNull([message], object)Check if the object is not null
assertSame([String], expected, actual)Check if both variables refer to the same object
assertNotSame([String], expected, actual)Check that both variables refer not to the same object
assertTrue([message], boolean condition)Check if the boolean condition is true.

Download the source
The source code can be downloaded from here.