An interesting advantage of CUBA is the ability of developing and
testing components in an ordinary, non-distributed development
environment even if they are later supposed to run in a client-server
system based on Enterprise JavaBeans or AXIS web services. The key
improvement is of course CUBA's wired mode which allows fast
edit-compile-test cycles without deployment overhead and simple local
debugging as supported in every mentionable IDE tool. But beside that,
CUBA provides some additional features to simplify component testing
A well-known problem of EJB development is the automatic test of
components with local interfaces (corresponds to internal interfaces in CUBA).
According to the EJB standard, local interfaces can not be accessed
outside an application server, so usually a test unit either requires
an additional delegation component or must be implemented as an EJB
itself. Both approaches have important disadvantages: delegates with
remote interfaces loose the call-by-reference semantic of the local
component, and implementing test units as EJBs makes it difficult to
use
common test frameworks like JUnit.
CUBA's wired mode provides a facility to expose a component's local
interface to a client outside the container. Because direct lookup from
a client context is not allowed, the local interface of interest must
be looked up from a helper component and can simply be returned by one
of its methods. Although this is of course strictly forbidden in J2EE
environments, it perfectly works in CUBA's wired container for test
purposes. The following code illustrates the implementation of an
exposer helper component doing that job:
public class ExposerImpl
extends AbstractComponent implements Exposer { public InternalIF exposeInternalComp() throws RemoteException { try { InternalIF if = (InternalIF)getContext().getComponent("internalRef"); // never do this in a managed environment ;-) return if; } catch(Exception x) { throw new ComponentException(x.getMessage()); } } } |
The directory examples/test includes a more detailed example.
When testing one particular component, it is often helpful to use
mock implementations for dependent components in order to reliably
reproduce suitable test data. This is especially of interest if the
dependent components access remote systems like databases which
potentially have a poor performance and are difficult to configure for
particular test situations. In general, a component's "real"
implementation could be substituted by a mock implementation specifying
a different implementation class in the component's descriptor component-jar.xml.
Unfortunately this is a relatively static configuration mechanism which
is difficult to use in automatic tests.
As an alternative, CUBA's wired container provides a facility for
replacing implementation classes programmatically at application
start-up. The following code example demonstrates how to construct a
WiredClientContext from an
explicitely instanciated WiredContainer
which is instrumented with a mock implementation for a particular
component:
// Read an application
descriptor the default way WiredAppReader appReader = new WiredAppReader(null); Object[] maps = appReader.process(); // Find the description of component with global name "hello" ComponentMap cmap = (ComponentMap)maps[0]; WiredComponentDesc cdesc = cmap.lookup("hello"); // Replace the component descriptor by an equivalent copy, // including the alternative implementation class cmap.registerDescriptor(new WiredComponentDesc(cdesc, MyMock.class)); // Instanciate a WiredContainer using the modified component map WiredContainer container = new WiredContainer( new PrimitiveWiredInstanceFactory(), new PrimitiveDataSourceManager(), cmap, (ResourceFactoryMap)maps[1], (Map)maps[2]); // Instanciate a WiredClientContext from the container WiredClientContext context = new WiredClientContext(container); |
The directory examples/test includes
the convenience class TestClientContext
which encapculates this
instanciation and substitution process.
When making use of dependency injection, you must consider a weekness
of the EJB 3 standard which needs special treatment. As a difference to
former versions of the specification, dependency injection defines an
individual contract between the container and the component which is by
default not expressed by a
well-defined Java interface but by the component's implementation class
(by its annotated setter methods and injection members). In these
cases, mock implementation can only be added safely at runtime if they
are derived from the
component's real implementation. Because this is a quite unpleasant
limitation for the design of mock classes, CUBA allows to define
interface-based injection rather than classed-based. I.e. instead of
annotating method implementations
as injection setters, the method declarations
of an interface are annotated. The component implementation class now
must implement two interfaces
- the one defining the business methods and the other one defining the
component-container-contract. This implementation can be replaced by
any other mock implementation at runtime which implements the same
interfaces. The following example illustrates this feature based on
code annotations:
// Define injection
interface public interface MyInjectionI { @Resource public setMyDB(DataSource db); } |
// Define component
implementation class implementing the // business methods and the injection methods public class MyCompImpl implements MyCompI, MyInjectionI { private DataSource db; // Injection method implementation, must not be annotated... public setMyDB(DataSource db) { this.db = db; } // Business method implementations... // ... } |
<resource-ref
ref-name="myDB" interface="javax.sql.DataSource" injection-target="myDB" injection-interface="MyInjectionI"/> |
No matter how good a software ist tested, there are always some
problems which can not easily be reproduced and analysed in a local
development environment but only occur in the production system or in
distributed usage. These cases often require the "poor man's debugger",
i.e. instrumentation of components with diagnostic logging/tracing
calls and redeployment. Detailed instrumentation has the disadvantage
of poluting the code and reducing its readability. Aspect-oriented
instrumentation solves the problem of code polution in the most elegant
way but requires additional tools which are often difficult to use and
to integrate into the development process.
Since version 3 the EJB standard (and CUBA too) provides a kind of
simple aspect-oriented instrumentation mechanism by means of method interceptors. Interceptors
can be specified in a component's descriptor and intercept all
invocations of the component's interface methods. This feature makes it
very easy to instrument a component with a method-based tracing
facility. A method interceptor is specified in a component's descriptor
like this:
<?xml version="1.0"
encoding="UTF-8"?> <component-jar> <component> <component-name>MyComp</component-name> <external-interface>mypackage.MyCompIF</external-interface> <component-class>mypackage.MyComp</component-class> <interceptor class="tracing.Trace"> <around-invoke method="trace"/> </interceptor> </component> </component-jar> |
The specified class must be available on the classpath and must
provide the specified method with a particular signature, e.g.
public class Trace { public Object trace(InvocationContextI ic) throws Exception { logEnter(ic); try { Object result = ic.proceed(); logLeave(ic, result); return result; } catch(Exception x) { logException(ic, x); } } ... } |
Home | Introduction | Javadoc |