Tuesday, June 7, 2011

Test-Driven Development of Talend Open Studio Routines

A Talend Open Studio Routine is a collection of functions used for expressions in components like tMap or tRowGenerator.  If you develop custom routines, consider a test-driven methodology for the best quality, especially if making the routines available to the community on the Talend Exchange.

See BRules 1.0 on the Talend Exchange for the code described in this post:  http://goo.gl/opPko. 

Talend Routines

Talend Open Studio comes with a collection of routines like TalendDate.getCurrentDate() that can be used wherever a Java expression can be entered.  For example, getCurrentDate() can be called in a tMap's target to output today's date to a field.

These routines lend themselves to test-driven development.  Test-driven development starts with the test which is the specification for what the code needs to do.  Create a stub function, one that does nothing or returns nothing, then write several tests that call the stub function in a number of different ways.

BRules 1.0 Routine Packaged with Test Cases (Jobs)

For instance, the stub function

public static boolean all(Object..._objects) { return true; }

Can be called from a tJava containing tests like

assertTrue( BRules.all("one") );
assertFalse( BRules.all("") );   

This example will "pass" the first test case (the assertTrue) but will fail on the second because the stub unconditionally returns true.

The stub is replaced with a real implementation.  As loops and logic are added to the implementation, you can verify whether or the routine works by re-running the tests.

Here is the full implementation

public static boolean all(Object..._objects) {
 boolean allSet = true;
 if( _objects == null ) return false;
 for( Object obj : _objects) {
   if( obj instanceof String ) {
     if( StringUtils.isEmpty((String)obj) ) {
       allSet = false;
       break;
     }
   }
   else {
     if( obj == null ) {
       allSet = false;
       break;
     }
   }
 }
 return allSet;
}
 

Running the tests with the implementation is successful and doesn't throw any exceptions.

JUnit

JUnit is a test framework that is popular with Java programmers because of it's small footprint.  The framework is packaged in a single JAR and supplies a useful set of assertion functions for testing conditions like assertTrue, assertEquals, and assertNotNull.  This eliminates code that checks these condition and handles failures by throwing exceptions.  

To use the assertions, drag a tJava onto the canvas.  Add the Java imports to the "Advanced settings" tab.

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;



Note that the junit module must have been installed in a prior import.  BRules-1.0 ships with junit-4.8.1.jar.

A BRules Test Case

Assert
 
Since the routines contain functions, single line tests are used to verify the different conditions.  A successful run will show no output.  A failure -- a condition that is not met, possibly negative -- will throw an exception.

assertFalse( BRules.all((Object[])null) );
assertTrue( BRules.all("one") );
assertTrue( BRules.all("one", "two") );
assertTrue( BRules.all("one", "two", new Long(0L)) );
assertFalse( BRules.all("") );
assertFalse( BRules.all("one", "") );
assertFalse( BRules.all("", "two") );
assertFalse( BRules.all(new Long(0L), null) );


"assertNotNull" and "assertNull" are two other useful functions.  If the arguments evaluate to null (for NotNull), an exception will be thrown to TOS.

This syntax uses a static import which removes the need for the class name.  Without the static import, each of these method calls would need to be prepended with the classname: Assert.assertFalse().

Logical Grouping

A different job can be used for the different methods.  BRules has several methods, some of which are overridden (repeated with different arguments).  Each method in all of its forms is put into a single job.  toCharset() can be called in three different ways; all of the toCharset() tests are put in the toCharset_TestJob.

Packaging

When the routines are exported, include the test jobs to serve as a post-install check.  This is also useful when testing across multiple versions of Talend Open Studio.  Import a zip file into a different version and run the test jobs.

The test jobs can be grouped into a "test suite" which is a parent job that will call the test jobs with a tRunJob.

Talend Open Studio Test Suite
Test-driven development is widely used in the Java community for applications because logic is such an important maintenance focus.  This importance applies to custom routine development in Talend Open Studio.  A well-tested, useful routine will have wide use on the Talend Exchange.

3 comments:

  1. Can you elaborate about how the exceptions can be caught and a meaningful output be generated in the TOS console? In my tJava I'm doing the following but the exception doesn't get caught

    try {
    assertEquals("Sample","some string","some other string");
    } catch(Exception ex){
    System.out.println("Sample failed");
    }

    ReplyDelete
  2. Never mind I figured out. I had to catch the particular exception org.junit.ComparisonFailure . So i did catch(org.junit.ComparisonFailure ex) in the above and it started working!

    ReplyDelete
    Replies
    1. Glad you resolved this. A ComparisonFailure isn't an Exception (it's an Error), although ComparisonFailure is handled in a try/catch block.

      Usually java.lang.Errors indicate a fatal system condition that should be thrown to the top of the calling program. I suspect that the JUnit developers are using ComparisonFailure for this purpose to not interfere with any Exception handling unit test code.

      Delete