Guide - PUnit

Introduction

Phoenix has a component/lifecycle aware unit test framework called PUnit. It has no requirements on external meta information. As such it could be used for unit testing for a wide range of Avalon-Framework enabled components. Having said that, usage requires some knowledge of multi component applications and the order of component lifecycling. PUnit builds on JUnit. You should have testing experience of JUnit before using PUnit.

Example Usage

There is a unit test framework for Phoenix called PUnit. It is used for pseudo in-container testing of Phoenix components. The main class PUnitTestCase should be extended by the developer, some manual setup done, then normal assertXXX() testing.

Test component (from Phoenix's own PUnit TestCase)

public class TestBlock
        implements Serviceable, Configurable, Initializable, Contextualizable, LogEnabled
{

    public ServiceManager m_serviceManager;
    public boolean m_initialized;
    public Context m_context;
    public Logger m_logger;
    public Configuration m_configuration;

    public void service( final ServiceManager serviceManager ) throws ServiceException
    {
        m_logger.info("service");
        m_serviceManager = serviceManager;
    }

    public void initialize() throws Exception
    {
        m_logger.warn("initialize");
        m_initialized = true;
    }

    public void contextualize(Context context) throws ContextException
    {
        m_logger.error("contextualize");
        m_context = context;
    }

    public void enableLogging(Logger logger)
    {
        m_logger = logger;
    }

    public void configure(Configuration configuration) throws ConfigurationException
    {
        m_logger.fatalError("configure");
        m_configuration = configuration;
    }


}      
      

Example TestCase (from Phoenix's own PUnit TestCase)

public class PUnitTestCaseTestCase extends PUnitTestCase
{

    DefaultConfigurationBuilder m_defaultConfigurationBuilder = new DefaultConfigurationBuilder();


    public PUnitTestCaseTestCase(String name)
    {
        super(name);
    }

    public void testBasicBlock() throws Exception
    {
        TestBlock block = new TestBlock();
        Configuration configuration = m_defaultConfigurationBuilder.build(
                new InputSource(new StringReader("<hi>Hi</hi>")));
        addBlock("bl","block", block, configuration);
        startup();
        // check lifecycle run thru
        assertNotNull("Configuration null", block.m_configuration);
        assertNotNull("Context null", block.m_context);
        assertNotNull("Logger null", block.m_logger);
        assertNotNull("ServiceManager null", block.m_serviceManager);
        assertTrue("Not Initialized", block.m_initialized);
        // check lifecycle events logged
        assertTrue("Service Not logged", super.logHasEntry("I:service"));
        assertTrue("Initialize Not logged", super.logHasEntry("W:initialize"));
        assertTrue("Contextualize Not logged", super.logHasEntry("E:contextualize"));
        assertTrue("Configure Not logged", super.logHasEntry("F:configure"));
        shutdown();
    }

}      
      

What is shown here is a single block being manually instantiated, and registered with PUnit (addBlock). Configuration rather than being from a file, is handed in from this source via DefaultConfigurationBuilder (see its other methods for more flexibility). The method startup() is causing the components (one only in this case) to be started. After the normal asserts have been invoked, the shutdown() is appropriate to tidy up in time for the next test. If you have multiple tests to do, it might be best to do all that is setup and tearDown methods.

It is important for developers to know the order of dependancy of their components, and in fact, the dependency needs of all components used in the test. This is normally the role of the assembler rather than the developer, but there is no way that unit testing can occur without all component needs being satisfied. Of course the developer can use mock implementations of the required services. Lastly, the order of dependency is important. Phoenix itself spends some effort determining which components are not needed by anything and cycles them first during startup. The last to be cycled are those that depend on the others.

To Use

When setting up a classpath, excalibur's containerkit and il8n jars are dependencies for Punit...

  <path id="test.class.path">
    <pathelement location="build/testclasses"/>
    <path refid="compile.classpath"/>
    <pathelement location="lib/phoenix-punit.jar"/>        
    <pathelement location="lib/excalibur-containerkit-1.0.jar"/>    
    <pathelement location="lib/excalibur-i18n-1.0.jar"/>
  </path>     
      

As with most testing frameworks, compilation of test classes is required before tests can be invoked...

      
     <target name="test" depends="compile" description="compiles and runs unit tests">
 
         <mkdir dir="build/testclasses"/>
 
         <javac 
             destdir="build/testclasses"
             debug="${build.debug}"
             optimize="${build.optimize}"
             deprecation="${build.deprecation}">
             <classpath refid="test.class.path" />
             <src path="src/test" />
         </javac>
 
         <mkdir dir="build/tests"/>
 
         <junit fork="true"
             haltonfailure="${junit.failonerror}"
             printsummary="yes"
             dir="build/tests">
             <classpath refid="test.class.path"/>
 
             <formatter type="plain"/>
 
             <batchtest todir="build/tests">
                 <fileset dir="build/testclasses">
                     <include name="**/*TestCase.class"/>
                     <exclude name="**/Abstract*"/>
                 </fileset>
             </batchtest>
         </junit>
 
    </target> 
      
by Paul Hammant