Tuesday, June 21, 2011

JUnit Tutorial with Examples

//JUnit test case
Functional testing, or integration testing, is concerned with the entire system, not just small 
pieces (or units) of code. It involves taking features that have been tested independently, combining 
them into components, and verifying if they work together as expected. For Java, this testing is 
typically performed using the JUnit framework.

This article introduces and demonstrates the following strategy for building an effective JUnit functional test suite:

    1. Identify use cases that cover all actions that your program should be able to perform.
    2. Identify the code's entry points - central pieces of code that exercise the functionality 
       that the code as a whole is designed to undertake.
    3. Pair entry points with the use cases that they implement.
    4. Create test cases by applying the initialize/work/check procedure.
    5. Develop runtime event diagrams and use them to facilitate testing. 
    6. Tests can rely on each other, but no single test should verify two things.

Translating Test Cases:
Each test case is divided into two parts: input and expected output. The input part lists all the test 
case statements that create variables or assign values to variables. The expected output part indicates 
the expected results; it shows either assertions or the message 'no exception' (when no assertions exist).

The basic input/output format is the simplest, easiest to understand model to follow for test cases. 
It follows the pattern of normal functions (pass arguments, get return value), and most user actions 
(press this button to start this test action). The pattern, then, is to:

   1. Initialize: Create the environment that the test expects to run in. The initialization code can 
      either be in the beginning of the test or in the setUp() method.
   2. Work: Call the code that is being tested, capturing any interesting output and recording any 
      interesting statistics.
   3. Check: Use assert statements to ensure that the code worked as expected. 
   
Example For Testing TOPUP transactions:
======================
//TestData.java
package com.ewp.test.data;
public class TestData {
    private String mobileNumber;
    private String targetMobileNumber;
    
    public String getMobileNumber() {
        return mobileNumber;
    }
    public void setMobileNumber(String mobileNumber) {
        this.mobileNumber = mobileNumber;
    }
    public String getTargetMobileNumber() {
        return targetMobileNumber;
    }
    public void setTargetMobileNumber(String targetMobileNumber) {
        this.targetMobileNumber = targetMobileNumber;
    }
}

======================
//testContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName"><value>oracle.jdbc.driver.OracleDriver</value></property>
        <property name="url"><value>jdbc:oracle:thin:@10.200.101.45:1521:obdbdev</value></property>
        <property name="username"><value>nkq4dev</value></property>
        <property name="password"><value>nkq4dev</value></property>
    </bean>
    
    <bean id="testData" class="com.ewp.test.data.TestData">
        <property name="mobileNumber" value="919686691376"/> 
        <property name="targetMobileNumber" value="919752890670"/>
    </bean>
</beans>
======================
//TopUpTest.java
import java.math.BigDecimal;
import java.util.Random;
import javax.sql.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import com.ewp.test.data.TestData;
import junit.framework.TestCase;

public class TopUpTest extends TestCase {
    final String PIN = "1111";
    private static ApplicationContext context;
    private static JdbcTemplate jdbcTemplate;
    private static TestData testData;
    private static DataSource dataSource;
    private static String mobileNumber = null;
    private static String targetMobileNumber = null;
    private static String topupAmount = 40;    

    static {
        context = new ClassPathXmlApplicationContext("testContext.xml");
        testData = (TestData) context.getBean("testData");        
        mobileNumber = testData.getMobileNumber();
        targetMobileNumber = testData.getTargetMobileNumber();    
    }
    /** ************ IMPLEMENTATION LEVEL TESTING *************** */

    @Override
    protected void setUp() throws Exception {
        dataSource = (DataSource) context.getBean("dataSource");
        jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    protected void tearDown() throws Exception {
        if (dataSource != null) {
            dataSource.getConnection().close();
        }
    }

    private TopUpServiceInterface getTopUpProxy() {
        TopUpServiceInterface paySvc = null;
        paySvc = (TopUpServiceInterface) ServiceProxy.getServiceProxy(TopUpServiceInterface.SERVICE_NAME);
        return paySvc;
    }

    //Mbill/Silver/Merchant,Correct pin
    public void testTopUpNumberCorrectPin() {
        TopUpServiceInterface impl = (TopUpServiceInterface) getTopUpProxy();
        try {
            TopUpSummary summary = impl.processTopup(mobileNumber,PIN, new BigDecimal(topupAmount), targetMobileNumber);
            int status = Integer.parseInt(summary.getStatus());
            assertEquals(summary.getErrorMessage(), 1, status);
        } catch (Exception exception) {
            fail(exception.toString()); //fail() is a method of Assert class in JUnit lib.
        }

    }
}
======================================
//Another test case methods with different return types/features
===============================
public void testUpgradeAccountForLockedAssistant()    throws EWPServiceException {
        try {
            token = TestUtility.getSecurityToken(testData.getTargetMobileNumber(), PIN);
            upgradeAcct(testData.getMobileNumber());
        } 
        catch (EWPServiceException e) {
            e.printStackTrace();
            assertEquals("BE2311", e.getErrorCode());            
        }
}
==============================
public void testDeleteBiller() {
        Boolean retVal = null;
        try {
            retVal = billPayServiceInterface.deleteBiller(testData.getMobileNumber(), new Long(12));
            LogHelper.error(CLASS_NAME, METHOD_NAME, "Delete Biller Status :["+ retVal+"]");
        } catch (com.ewp.core.exceptions.BusinessException e) {
            assertTrue(e instanceof com.ewp.core.exceptions.BusinessException);
        } catch (Exception e) {
            fail();
            e.printStackTrace();
        }
}
=============================
public void testIssueCard() {
        try {    
            NokiaPayAtmCardTest securitytoken = new NokiaPayAtmCardTest();            
            test.isAssistantActiveByMobileNumber(CommonHelpers.normalizePhoneNumber(assistantMobileNumber));
            test.issueCard(assistantMobileNumber, PIN,assistantMobileNumber, silverMobile, cardRefNumber1);
            assertTrue(true);
        }
        catch (Exception e) {

            e.printStackTrace();
            fail(e.getMessage());
        }
}
==============================

Now Again JUnit details:
junit.framework.Test (Interface)
Method Summary (only two methods):
 int     countTestCases()       ==> Counts the number of test cases that will be run by this test.
 void     run(TestResult result) ==> Runs a test and collects its result in a TestResult instance.

All Known Implementing Classes:
    ActiveTestSuite, JUnit4TestAdapter, JUnit4TestCaseFacade, 
    RepeatedTest, TestCase, TestDecorator, TestSetup, TestSuite 

==============================
public abstract class TestCase extends Assert implements Test

A test case defines the fixture to run multiple tests. To define a test case 
 1. implement a subclass of TestCase
 2. define instance variables that store the state of the fixture
 3. initialize the fixture state by overriding setUp()
 4. clean-up after a test by overriding tearDown().

Each test runs in its own fixture so there can be no side effects among test runs. Here is an example:

public class MathTest extends TestCase {
    protected double fValue1;
    protected double fValue2;

    protected void setUp() {
       fValue1= 2.0;
       fValue2= 3.0;
    }
}

For each test implement a method which interacts with the fixture. Verify the expected results with 
assertions specified by calling Assert.assertTrue(String, boolean) with a boolean.

    public void testAdd() {
       double result= fValue1 + fValue2;
       assertTrue(result == 5.0);
    }
====================================================

Once the methods are defined you can run them. The framework supports both a static type safe and 
more dynamic way to run a test. In the static way you override the runTest method and define the method 
to be invoked. A convenient way to do so is with an anonymous inner class.

TestCase test= new MathTest("add") {
    public void runTest() {
       testAdd();
    }
};
test.run();
--------------- 
The dynamic way uses reflection to implement runTest(). It dynamically finds and invokes a method. 
In this case the name of the test case has to correspond to the test method to be run.

TestCase test= new MathTest("testAdd");
test.run();
--------------- 

The tests to be run can be collected into a TestSuite. JUnit provides different test runners which 
can run a test suite and collect the results. A test runner either expects a static method suite as 
the entry point to get a test to run or it will extract the suite automatically.

//public class TestSuite extends java.lang.Object implements Test

A TestSuite is a Composite of Tests. It runs a collection of test cases. Here is an example using the 
dynamic test definition.

TestSuite suite= new TestSuite();
suite.addTest(new MathTest("testAdd"));
suite.addTest(new MathTest("testDivideByZero"));
 
Alternatively, a TestSuite can extract the tests to be run automatically. To do so you pass the class 
of your TestCase class to the TestSuite constructor.

TestSuite suite= new TestSuite(MathTest.class);

This constructor creates a suite with all the methods starting with "test" that take no arguments.
A final option is to do the same for a large array of test classes.

Class[] testClasses = { MathTest.class, AnotherTest.class }
TestSuite suite= new TestSuite(testClasses);

//Another example
public class TestA extends UITestCase { ... }

public class TestB extends UITestCase { ... }

public class TestC extends UITestCase { ... }

//Putting them in a suite is as simple as defining a class like this:
public class MyTestSuite extends TestCase {
        public static TestSuite suite() {
            TestSuite suite = new TestSuite();       
            suite.addTestSuite(TestA.class);
            suite.addTestSuite(TestB.class);
            suite.addTestSuite(TestC.class);
            return suite;
        }
}
-----------------------------------------------
Few more Test methods:
    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.
==============================================END====================================================

No comments:

Post a Comment