Tuesday, June 14, 2011

JDeveloper: Unit Testing with JUnit 4.x in JDeveloper 11g

This article provides a quick overview of JUnit integration JDeveloper 11g

Main Topics

  • Verify the JUnit Extension in JDeveloper
  • Create a parallel source directory for tests   (Optional)
  • Adding a JUnit Test Case to the Java Project
  • Implementing a Test Case
  • Running the JUnit Test Cases
Verify the JUnit Extension in JDeveloper

JUnit functionality is an optional extension to JDeveloper 11g So the first step is to verify that the extension is installed.
Start JDeveloper and from the main menu choose Help->About  Select the Extensions tab and verify JUnit is listed. 
Confirm JUnit Integration listed.
If its not listed, add it by choosing Help->Check For Updates and select JUnit from the available updates. 
After installation restart JDeveloper


Create a parallel source directory for tests (Optional)

Test Classes should be located in the same java package as the classes they test.
But it may be undesirable to mix the test and target classes in deployment units and version control.

One solution is to create a parallel source hierarchy so that the same java packages exist under two different directories.

Follow these steps:
Right click on a Java Project in JDeveloper and choose Project Properties. In the Project Properties dialog, select the Project Source Paths node.

The Project Source paths field will indicate the current source path for the project For example c:\projects\myApp\JavaProject\src Create a new directory in the file system outside JDeveloper 

For example in the case shown below a directory named c:\projects\XPathFunctionsExample\JavaProject\test

File_Explorer


Return to the JDeveloper Project Properties dialog and click the Add button to add an additional source path to the project. Add the path to the newly created directory. Click the up button to move the new test folder to the first entry in the list.

 Note: The order of the directories in the list is important.  When new Java Classes for this project are created they will be placed in the directory named indicated by the top entry in the list.


The current setting will add new classes to the test directory, which is correct for creating Test Case classes.


Change the order of the list to have the src folder first when creating Classes that are not used for testing. The final configuration should appear as below:


Project_Properties
In the JDeveloper Application explorer the files in both the src and test directories will be displayed in a single hierarchy

Application_Explorer_Showing_single_Source_tree


Adding a JUnit Test Case to the Java Project
 

Right click on the Java Project in the Application Navigator and choose New.
In the Gallery Dialog choose the All Technologies Tab Expand the General Category and select Unit Tests Select the Test Case item and press the OK button.

New_Gallery_Dialog

The Create Test Case wizard should appear.
In Step one, click the Browse button and navigate to the java package containing the Class to be tested.

New_Test_Case_Class_Selection

Check the Classes and methods that will be tested by the new Test Case. Press the Next Button

Test_Case_Wizard

In Step 2 set a name for the new Test Class and set the package. Select the setUp() and tearDown() methods.

Create_Test_Case_Wizard_Step_2

 In Step 3 press the finish button.

(Optional note regarding Test Fixtures)
This dialog also supports the selection of an existing Test Fixture Class. A Test Fixture is a JUnit class that creates test data to be shared between a number of test classes.

If a Fixture is desirable, create Fixture before the Test Class by right clicking on the Java Project, choose New,  then in the Gallery Dialog choose Test Case and press the OK button.

New_Test_Case_Step_3

Note:  If compile errors occur for Junit classes, add the JUnit library to the Project as follows.:


Right click on the Java Project and Choose Project Properties In the left side of the Project Properties window select "Libraries and Classpath" On the right side press the Choose Library button, Select the "JUnit 4 Runtime" library in the Extension category and press the OK button.

Add_Library

Implementing a Test Case

Below is an example of a Test Class that can be explained using any number of good tutorials on the web that cover JUnit.
package com.example.reusable.asset;

import java.io.StringReader;
import java.io.StringWriter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPathFunctionException;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;


/*
 * JUnit 4 Example
 * With JUnit 4 Test methods use @Test annotation and to no longer need to end with the word test 
 * and the Test Class no longer needs to extend TestCase
 * This simple example uses the local setup() and teardown() methods rather than using a TestFixture class
 */

public class SortEmployeesTest {
    
      Document doc1 = null;
      Document doc2 = null;
      Document doc3 = null;
      
      String xmlString1 = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" +
        "<foo></foo>";
      
      String xmlString2 = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + 
        "<ns1:Employees xmlns:ns1=\"http://www.example.org\">" +
        "</ns1:Employees>";
      
      String xmlString3 = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + 
        "<ns1:Employees xmlns:ns1=\"http://www.example.org\">" +
         "<ns1:Emp>" +
          "<ns1:number>190</ns1:number>" +
          "<ns1:firstName>Bob</ns1:firstName>" +
          "<ns1:lastName>Jones</ns1:lastName>" +
          "<ns1:job>Accountant</ns1:job>" +
          "<ns1:hiredate></ns1:hiredate>" +
          "<ns1:dept>109</ns1:dept>" +
        "</ns1:Emp>" +
        "<ns1:Emp>" +
          "<ns1:number>100</ns1:number>" +
          "<ns1:firstName>Peter</ns1:firstName>" +
          "<ns1:lastName>Baines</ns1:lastName>" +
          "<ns1:job>President</ns1:job>" +
          "<ns1:hiredate></ns1:hiredate>" +
          "<ns1:dept>102</ns1:dept>" +
        "</ns1:Emp>" +
        "<ns1:Emp>" +
          "<ns1:number>120</ns1:number>" +
          "<ns1:firstName>James</ns1:firstName>" +
          "<ns1:lastName>Last</ns1:lastName>" +
          "<ns1:job>Service Advisor</ns1:job>" +
          "<ns1:hiredate></ns1:hiredate>" +
          "<ns1:dept></ns1:dept>" +
        "</ns1:Emp>" +
        "</ns1:Employees>";


    public SortEmployeesTest() {
    }


    @Before
    public void setUp() throws Exception {
        
            DocumentBuilderFactory factory =  DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            DocumentBuilder builder = null;
            
            builder = factory.newDocumentBuilder();
            
            // Create empty doc 
            InputSource iSource1 = new InputSource( new StringReader(xmlString1));
            doc1 = builder.parse(iSource1);
            
            // create no employees doc 
            InputSource iSource2 = new InputSource( new StringReader(xmlString2));
            doc2 = builder.parse(iSource2);
            
            // create full doc
            InputSource iSource3 = new InputSource( new StringReader(xmlString3));
            doc3 = builder.parse(iSource3);
              
    }

    @After
    public void tearDown() throws Exception {
    }


    /**
     * Test with Wrong document type, expect exception
     * @see SortEmployees#sortNodes(org.w3c.dom.Node,boolean)
     */
    @Test
    public void testWrongInputDocumentType() {
        try {
            SortEmployees.sortNodes(doc1.getFirstChild(), false);
            fail("sortNodes() accepted an invalid document type");
            printDocument(doc1);
        } catch (XPathFunctionException ex) {
           assertTrue(true);
        }
    }


    /**
     * Test Document with no Employees
     * @see SortEmployees#sortNodes(org.w3c.dom.Node,boolean)
     */
    @Test
    public void testNoEmployees() {
        try {
            SortEmployees.sortNodes(doc2.getFirstChild(), false);
          assertEquals("Found wrong number of Employees after sort. ", 0,
                       doc2.getFirstChild().getChildNodes().getLength());

        } catch (XPathFunctionException ex) {
            fail("sortEmployees() threw exception processing document with no employees");
            ex.printStackTrace(System.out);
            printDocument(doc2);

        }
    }


    /**
     * Test with Valid Document
     * @see SortEmployees#sortNodes(org.w3c.dom.Node,boolean)
     */
    @Test
    public void testValidDocument() {
        try {
            Node employees = doc3.getFirstChild();
            SortEmployees.sortNodes(doc3.getFirstChild(), false);

            // Total Employees
            assertEquals("Found wrong number of Employees after sort. ", 3,
                         employees.getChildNodes().getLength());

            //   System.out.println(employees.getFirstChild().getTextContent());
            //   System.out.println(employees.getFirstChild().getNextSibling().getTextContent());

            // Employee 1 Dept expecting ""
            assertEquals("Employees in wrong order after sort. ", "",
                         employees.getFirstChild().getLastChild().getTextContent());

            // Employee 24 Dept expecting 102
            assertEquals("Employees in wrong order after sort. ", "102",
                         employees.getFirstChild().getNextSibling().getLastChild().getTextContent());

            // Employee 3 Dept expecting 109
            assertEquals("Employees in wrong order after sort. ", "109",
                         employees.getFirstChild().getNextSibling().getNextSibling().getLastChild().getTextContent());


         printDocument(doc3);

        } catch (XPathFunctionException ex) {
            fail("sortEmployees() threw exception processing valid document");
            ex.printStackTrace(System.out);
            printDocument(doc3);
        }
    }
    
  public void printDocument(Document doc)  {
       // Print the Node to Stdout
      try {
         javax.xml.transform.TransformerFactory tfactory = TransformerFactory.newInstance();
         javax.xml.transform.Transformer xform = tfactory.newTransformer();
         xform.setOutputProperty(OutputKeys.INDENT, "yes");
        //   xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
         
         javax.xml.transform.Source src= new DOMSource(doc);
         java.io.StringWriter writer = new StringWriter();
         Result result = new StreamResult(writer);   
         xform.transform(src, result);
         System.out.println(writer.toString());
      }
      catch (TransformerConfigurationException tc) {
         System.out.println("printDocument: Unable to ouput document");
         tc.printStackTrace (System.out);
      }
      
      catch (TransformerException te) {
        System.out.println("printDocument: Unable to ouput document");
        te.printStackTrace (System.out);
      }
       
   }
}
-----------------------------------------------------
Here is the Class to be tested
package com.example.reusable.asset;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import javax.xml.xpath.XPathFunctionException;
import oracle.fabric.common.xml.xpath.IXPathContext;
import oracle.fabric.common.xml.xpath.IXPathFunction;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/** Mapper function that can be used by any SOA Suite component.
 *
 **/

 public class SortEmployees implements IXPathFunction {
 
 public Object call(IXPathContext context, List args) {
 
 org.w3c.dom.Node employees = (Node) args.get(0);
 System.out.println("SortEmployees Retrieved employees ");
 try {
 
 sortNodes(employees, false);
 }
 catch (XPathFunctionException ex)
 {
 System.err.println("Error: SortEmployees XPath function exception, skipped processing: " + 
 ex.getMessage());
 employees = (Node) args.get(0);
 } 

 return employees;
 }
 
 /**
 * Sorts the nodes in a node list
 * 
 * @param nodeList - list whose nodes will be sorted
 * @param descending - true for sorting in descending order
 */
 public static Node sortNodes(Node inputNode, boolean descending) throws XPathFunctionException {

 
 List nodes = new ArrayList();
 
 if(inputNode==null) {
 throw new XPathFunctionException("Input Node is null");
 }
 else { 
 if(inputNode == null)
 throw new XPathFunctionException("Expected input Node of type http://www.example.org : Employees " +
 "received null document");
 if(inputNode.getNamespaceURI() == null ||
 !(inputNode.getNamespaceURI().equals("http://www.example.org")) ||
 !inputNode.getLocalName().equals("Employees") )
 throw new XPathFunctionException("Expected input Node of type http://www.example.org : Employees " +
 "received " + inputNode.getNamespaceURI() + " : " + inputNode.getLocalName());
 
 NodeList employees = inputNode.getChildNodes();
 // System.out.println("Found " + employees.getLength() + " Employees");
 
 if (employees.getLength() > 0) {
 for (int i = 0; i < employees.getLength(); i++) {
 Node tNode = employees.item(i);
 nodes.add(tNode);
 }
 
 Comparator comp = new EmpDeptComparator();
 
 if (descending)
 {
 //if descending is true, get the reverse ordered comparator
 Collections.sort(nodes, Collections.reverseOrder(comp));
 } else {
 Collections.sort(nodes, comp);
 }
 
 for (Iterator iter = nodes.iterator(); iter.hasNext();) {
 Node element = (Node) iter.next();
 inputNode.appendChild(element);
 }
 }
 }
 
 return inputNode;
 
 }
 }

 class EmpDeptComparator implements Comparator {

 public int compare(Object arg0, Object arg1) {
 return ((Node) arg0).getFirstChild().getTextContent().compareTo(
 ((Node) arg1).getLastChild().getTextContent());
 }
 
 }


Running the JUnit Test Cases 
Tests can be run from within JDeveloper without creating a JUnit Test Suite.


Simply right click on the Java Project containing the Tests and choose 


Starting_Tests_From_Junit


Test Results are shown in the  JUnit Test Runner Window as shown


Test_Summary


JUnit 4 Test Suite Class (Optional)


The following test Suite class is not required if tests are run solely within JDeveloper.
The class does support running the tests from the Java Command line or Ant


package com.example.reusable.asset;
import org.junit.runner.JUnitCore;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

/*
 * JUnit 4 Test Suite
 * List Test cases as comma seperated list in @Suite.SuiteClasses annotation
 * Right click and choose run within JDeveloper
 * Or run from java command line or Ant using main method
 * 
 * This class is not necessary if testing solely within JDeveloper
 */
@RunWith(Suite.class)
@Suite.SuiteClasses( { com.example.reusable.asset.SortEmployeesTest.class })
public class AllTestsSuite {
 
 public static void main(String[] args) {
 JUnitCore.runClasses(new Class[] { AllTestsSuite.class });
 }

}

JUnit Test Fixture Example  (Optional)


A test fixture centralizes the creation of test data to be used by multiple Test Classes or Developers of different Test Classes Following is the example from above modified to use a Test Fixture

Test Fixture Class:

package com.example.reusable.asset;
import java.io.IOException;
import java.io.StringReader;

import java.io.StringWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import javax.xml.parsers.ParserConfigurationException;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class UnitTestFixture {
    
  
  Document doc1 = null;
  Document doc2 = null;
  Document doc3 = null;
  
  String xmlString1 = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" +
      "<foo></foo>";
  
  String xmlString2 = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + 
      "<ns1:Employees xmlns:ns1=\"http://www.example.org\">" +
      "</ns1:Employees>";
  
  String xmlString3 = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" + 
      "<ns1:Employees xmlns:ns1=\"http://www.example.org\">" +
       "<ns1:Emp>" +
        "<ns1:number>190</ns1:number>" +
        "<ns1:firstName>Bob</ns1:firstName>" +
        "<ns1:lastName>Jones</ns1:lastName>" +
        "<ns1:job>Accountant</ns1:job>" +
        "<ns1:hiredate></ns1:hiredate>" +
        "<ns1:dept>109</ns1:dept>" +
      "</ns1:Emp>" +
      "<ns1:Emp>" +
        "<ns1:number>100</ns1:number>" +
        "<ns1:firstName>Peter</ns1:firstName>" +
        "<ns1:lastName>Baines</ns1:lastName>" +
        "<ns1:job>President</ns1:job>" +
        "<ns1:hiredate></ns1:hiredate>" +
        "<ns1:dept>102</ns1:dept>" +
      "</ns1:Emp>" +
      "<ns1:Emp>" +
        "<ns1:number>120</ns1:number>" +
        "<ns1:firstName>James</ns1:firstName>" +
        "<ns1:lastName>Last</ns1:lastName>" +
        "<ns1:job>Service Advisor</ns1:job>" +
        "<ns1:hiredate></ns1:hiredate>" +
        "<ns1:dept></ns1:dept>" +
      "</ns1:Emp>" +
      "</ns1:Employees>";



    public UnitTestFixture() {
    
        
    }

    public void setUp()  throws ParserConfigurationException, SAXException, IOException {
      DocumentBuilderFactory factory =  DocumentBuilderFactory.newInstance();
      factory.setNamespaceAware(true);
      DocumentBuilder builder = null;
      
      builder = factory.newDocumentBuilder();
      
      // Create empty doc 
      InputSource iSource1 = new InputSource( new StringReader(xmlString1));
      doc1 = builder.parse(iSource1);
      
      // create no employees doc 
      InputSource iSource2 = new InputSource( new StringReader(xmlString2));
      doc2 = builder.parse(iSource2);
      
      // create full doc
      InputSource iSource3 = new InputSource( new StringReader(xmlString3));
      doc3 = builder.parse(iSource3);
        
    }

    public void tearDown() {
    }
    
  
 public void printDocument(Document doc)  {
      // Output the modified Node
     try {
        javax.xml.transform.TransformerFactory tfactory = TransformerFactory.newInstance();
        javax.xml.transform.Transformer xform = tfactory.newTransformer();
        xform.setOutputProperty(OutputKeys.INDENT, "yes");
     //   xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
        
        javax.xml.transform.Source src= new DOMSource(doc);
        
        java.io.StringWriter writer = new StringWriter();
        Result result = new StreamResult(writer);
               
        xform.transform(src, result);
        System.out.println(writer.toString());
     }
     catch (TransformerConfigurationException tc) {
        System.out.println("printDocument: Unable to ouput document");
        tc.printStackTrace (System.out);
     }
     
     catch (TransformerException te) {
       System.out.println("printDocument: Unable to ouput document");
       te.printStackTrace (System.out);
     }
      
  }

    public Document getDoc1() {
        return doc1;
    }

    public Document getDoc2() {
        return doc2;
    }

    public Document getDoc3() {
        return doc3;
    }
}