This document provides information for Unit Testing and Integration Testing of your services using Rio. Rio provides classes to assist in the setup and execution of services for either testing approach. Rio itself has test cases that use the integration testing framework built on JUnit. We will review this framework in this section.
Rio provides integration testing, allowing the integration testing of your services together; with or without the the instantiation of Rio infrastructure services. This section will discuss both options.
Unit testing services using Rio can be done using one of many testing frameworks. The examples provided with Rio use JUnit and the Failsafe Maven plugin. The goal going into this is to unit test your service without having to instantiate the Rio infrastructure services (Provision Monitor, Cybernode, Lookup).
The example below illustrates how an opstring composed of multiple services that use associations can be tested. In this scenario, the Calculator service has the Addition, Subtraction, Multiply and Divide services injected into it. All of these services are created and injected as they would be in an actual Rio deployment, but in our scenario they are bootstrapped locally.
This occurs because Rio provides an extension for a local LookupCache to be used, one that is wired into the lifecycle events of a service being instantiated by a Cybernode.
/**
* Example testing the Calculator service and it's required services from the
* OperationalString
*/
@RunWith(Parameterized.class)
public class ITCalculatorTest extends ITAbstractCalculatorTest {
String opstring;
Calculator calculator;
@Parameterized.Parameters
public static Collection<Object[]> data() {
String opstring = System.getProperty("opstring");
Assert.assertNotNull("no opstring given", opstring);
return Arrays.asList(new Object[][] {{ opstring }});
}
public ITCalculatorTest(String opstring) {
this.opstring = opstring;
}
@Before
public void setupCalculator() throws Exception {
StaticCybernode cybernode = new StaticCybernode();
Map<String, Object> map = cybernode.activate(new File(opstring));
for (Map.Entry<String, Object> entry : map.entrySet()) {
String beanName = entry.getKey();
Object beanImpl = entry.getValue();
if (beanName.equals("Calculator"))
calculator = (Calculator) beanImpl;
}
}
@Test
public void testBean() throws RemoteException {
testService(calculator);
}
}
In the setupCalculator() method, the org.rioproject.cybernode.StaticCybernode is created and passed the opstring provided as a property when configuring the Failsafe plugin.
The StaticCybernode instantiates the service(s) declared in the opstring, and returns a java.util.Map composed of key value pairs mapping to the ServiceBean names, and each respective service implementation.
In order to used the returned service implementation, you must cast it accordingly.
StaticCybernode.activate() can instantiate all the services
in your opstring or just specific services. If you want to create
specific services, pass in the name (as found in the opstring) for
the service(s) you want.
The corresponding opstring for the Calculator example is shown below. Note the association declarations.
import org.rioproject.config.Constants
deployment(name:'Calculator') {
groups System.getProperty(Constants.GROUPS_PROPERTY_NAME,
System.getProperty('user.name'))
artifact id:'service', 'org.rioproject.examples:calculator:1.0'
artifact id:'service-dl', 'org.rioproject.examples:calculator:dl:1.0'
service(name: 'Calculator') {
interfaces {
classes 'org.rioproject.examples.calculator.Calculator'
artifact ref:'service-dl'
}
implementation(class:'org.rioproject.examples.calculator.service.CalculatorImpl') {
artifact ref:'service'
}
associations {
['Add', 'Subtract', 'Multiply', 'Divide'].each { s ->
association name:"$s", type:'requires', property:"${s.toLowerCase()}"
}
}
maintain 1
}
['Add', 'Subtract', 'Multiply', 'Divide'].each { s ->
service(name: s) {
interfaces {
classes "org.rioproject.examples.calculator.$s"
artifact ref:'service-dl'
}
implementation(class: "org.rioproject.examples.calculator.service.${s}Impl") {
artifact ref:'service'
}
maintain 1
}
}
}
It is important to remember that the StaticCybernode utility
requires that all necessary classes (and resources) are in the classpath
of the JVM in order to activate the service bean(s). Although the opstring(s)
may have resources/artifacts defined, all classes must be in the classpath of
the JVM.
The Rio test infrastructure provides a way to bootstrap core Rio infrastructure services (Provision Monitor, Cybernode, Lookup Service, Webster) as part of your integration test case. The test infrastructure is built on JUnit 4.5+, and is what Rio itself uses as part of it's supplied project tests.
When writing a JUnit test case, your test case code needs to simply use the following annotation:
@RunWith(RioTestRunner.class)
public class MyTest {
...
}
The org.rioproject.test.RioTestRunnner class is a custom extension of org.junit.runners.BlockJUnit4ClassRunner that provides the functionality of the Rio Test Framework to standard JUnit 4.5+ tests.
The RioTestRunnner also provides support for the following annotations:
@SetTestRunner
Defines the property to invoke to set the TestManager. The TestManager will be
injected by the RioTestRunner prior to test case method invocation.
public class MyTest {
@SetTestManager
static TestManager testManager;
@BeforeClass
public static setup() {
Assert.assertNotNull(testManager);
testManager.startReggie();
}
@Test
public void testIt() {
...
}
...
}
The @SetTestManager annotation can be applied at either the method or field level.
@IfPropertySet
Annotation to indicate that a test is to be run if a specific property is set.
@IfPropertySet(name="run.long.test", value="true")
@Test
public void testSomethingThatTakesALongTime() {
...
}
The @IfPropertySet annotation can be applied at either the class or method level.
The Rio Test Manager is a Groovy class that requires a configuration file in order to initialize and run. By default the configuration file is loaded as a resource using reasonable defaults. The default configuration that ships with Rio is included below:
manager {
execClassPath =
'${RIO_HOME}${/}lib${/}boot.jar${:}${RIO_HOME}${/}lib/${/}start.jar${:}${JAVA_HOME}${/}lib${/}tools.jar${:}${RIO_HOME}${/}lib${/}groovy-all.jar'
inheritOptions = true
jvmOptions = '''
-XX:+UseConcMarkSweepGC -XX:+AggressiveOpts
-Djava.security.policy=${RIO_HOME}${/}policy${/}policy.all
-DRIO_HOME=${RIO_HOME} -Dorg.rioproject.groups=${org.rioproject.groups}'''
mainClass = 'com.sun.jini.start.ServiceStarter'
/* The ${service} token will be replaced by the name of the starter file.
* For start-reggie the service name will be reggie, for start-monitor the
* service name will be monitor, etc ... */
String logExt = System.getProperty(Constants.GROUPS_PROPERTY_NAME,
System.getProperty('user.name'))
String logDir = '/tmp/logs/rio/'
String opSys = System.getProperty('os.name')
if(opSys.startsWith("Windows"))
logDir = '${java.io.tmpdir}/logs/rio/'
log = logDir + logExt + '/${service}.log'
/*
* Remove any previously created service log files
*/
cleanLogs = true
monitorStarter = '${RIO_HOME}/config/start-monitor.groovy'
cybernodeStarter = '${RIO_HOME}/config/start-cybernode.groovy'
}
The configuration file can be overridden by setting the following system property:
-Dorg.riproject.test.manager.config=path-to-config-file
The properties of the TestManager configuration file are documented below:
| Property | Description |
|---|---|
| execClassPath | The classpath to use to start the core Rio services |
| inheritOptions | If set, and set to true, the options (input arguments) to the JVM will be inherited from the enclosing process that has created the TestManager. If this property is set to true, input arguments of the enclosing process will be used to exec service JVMs created by the Rio TestManager, and will also override any settings declared in the jvmOptions property below. |
| jvmOptions | Options and arguments to use when starting the JVM. For more details on JVM options, check here |
| log | Where to create a log for the service being started. The service token will be replaced by the name of the starter file. For start-monitor the service name will be monitor, etc .. |
| cleanLogs | Remove any previously created service log files |
| mainClass | The classpath to use to start the core Rio services |
| monitorStarter | The configuration file used to start the Provision Monitor. |
| cybernodeStarter | The configuration file used to start a Cybernode. |
| config | Optional configuration file that the RioTestRunner will use to confgure underlying utilities. |
| harvesterOpString | The opsting the harvest utility will use. The harvest utility is deployed to all CYbernodes and is used to collect (harvest) configured artifacts. You can read more about harvesting here. |
| harvestDir | The directory to save harvested artifacts. |
The Rio testing infrastructure is set up to use a Groovy configuration file to run and configure your test classes. The following snippet illustrates the elements involved:
ITCalculatorDeployTest {
groups = "Calculator"
/* Comma separated locator names */
//locators = "10.0.1.4:9160, 10.0.1.6:32189"
//testManager = new org.rioproject.test.TestManager(true)
numCybernodes = 1
numMonitors = 1
opstring = 'src/main/conf/calculator.groovy'
autoDeploy = true
//harvest = true
//timeout
}
The enclosing element of the configuration must be the class name of the test class sans the package name. This allows the Rio testing infrastructure to locate the configuration for the test class.
| Property | Description |
|---|---|
| groups | Lookup discovery group(s) to use when creating discovery management for the test case. |
| locators | Lookup locator(s) to use when creating discovery management for the test case. |
| testManager | Optionally decare the TestManager to use. If not declared a new org.rioproject.test.TestManager(true) is created. |
| numCybernodes | Optional number of Cybernodes the test infrastructure should create |
| numMonitors | Optional number of Provision Monitors the test infrastructure should create. |
| opstring | The OperationalString associated with the test case. |
| autoDeploy | Whether to automatically deploy the associated opstring (above) when the test case is started. |
| harvest | Whether to harvest artifacts at the end of the test classes execution (run with all after class statements). |
| timeout | The default timeout (per test method and in millis) to use for test case execution.
If JUnit's @Test(timeout="xxx") is used, JUnit's declaration takes precedence.
|