OSGi : Junit Test Extender Using Fragment and BundleTracker
Join the DZone community and get the full member experience.
Join For FreeI-Introduction :
As Service tracker, the Osgi 4.2 release introduced "BundleTracker" which simplifies working with bundles and facilitate building extenders. In this article, we will try to write a simple JUnit test extender using fragments (to separate tests from source code). We will give to the user opportunities to automate the unit test just after or when the fragment bundle is installed, plus a console command to test all installed fragments or a specific one.II- BundleTracker
We will use BundleTrackerCustomizer to track only Resolved Bundle state as fragments will not be at started state and to be sure that this fragment has a resolved parent.bundleTracker = new BundleTracker(context, Bundle.RESOLVED, testExtender);
bundleTracker.open();
where TestExtender
1 - implements BundleTrackerCustomizer
2- check if it is a test fragment
private static final String TEST_HEADER = "Unit-Test";3- If yes, add (or update) it to map using it's bundle id.
public static final boolean isTestFragment(Bundle bundle) {
String header = bundle.getHeaders().get(TEST_HEADER) + "";
String fragment = bundle.getHeaders().get(org.osgi.framework.Constants.FRAGMENT_HOST) + "";
return (!"null".equals(header) && !"null".equals(fragment));
}
private Map bundles = Collections.synchronizedMap(new HashMap());
bundles.put(bundle.getBundleId(), bundle);
4- check if auto test is enabled :
public static final boolean isAutoTestEnabled(Bundle bundle) {
return "true".equals(bundle.getHeaders().get(TEST_HEADER) + "");
}
if (isAutoTestEnabled(bundle)) {
test(bundle.getBundleId());
}
III- Test
Let's define loadClass method to load class from bundle and getHostBundle to get the parent Bundlepublic static final Class loadClass(String clazz, Bundle bundleHost) {
try {
Class loadClass = bundleHost.loadClass(clazz);
return loadClass;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static final Bundle getHostBundle(BundleContext context,Bundle bundle) {loadClass and getHostBundle will be used to load all test class from fragment using hostBundle context!!!! (fragment will be loaded using it's host context loader) and try to load all class with Test suffix recursively.
String fragment = bundle.getHeaders().get(org.osgi.framework.Constants.FRAGMENT_HOST) + "";
Bundle[] bundles = context.getBundles();
for (Bundle ibundle : bundles) {
if (ibundle.getSymbolicName().equals(fragment)) {
return ibundle;
}
}
throw new RuntimeException();
}
public static final List> getTestClass(BundleContext context,Bundle bundle) {for each loaded test Class we inspect it to find junit annotated methods :
List> clazzs = new ArrayList>();
Enumeration entrs = bundle.findEntries("/", "*Test.class", true);
if (entrs == null || !entrs.hasMoreElements()) {
return Collections.EMPTY_LIST;
}
Bundle hostBundle = getHostBundle(context,bundle);
while (entrs.hasMoreElements()) {
URL e = (URL) entrs.nextElement();
String file = e.getFile();
String className = file.replaceAll("/", ".").replaceAll(".class", "").replaceFirst(".", "");
Class clazz = loadClass(className, hostBundle);
clazzs.add(clazz);
}
return clazzs;
}
public static final Test inspectClass(Class clazz) {and then, emulate junit mechanism using reflexion :
Test test = new Test();
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method : declaredMethods) {
if (method.isAnnotationPresent(org.junit.BeforeClass.class)) {
test.setBeforeClass(method);
}
if (method.isAnnotationPresent(org.junit.AfterClass.class)) {
test.setAfterClass(method);
}
if (method.isAnnotationPresent(org.junit.Before.class)) {
test.setBefore(method);
}
if (method.isAnnotationPresent(org.junit.After.class)) {
test.setAfter(method);
}
if (method.isAnnotationPresent(org.junit.Test.class)) {
test.addTest(method);
}
}
return test;
}
public static void testClass(Test testClass, Object object) {the object passed to this method is a new instance of class this is why don't forget ( DynamicImport-Package: *)
System.out.println("___________________________________________________________________________");
try {
try {
if (testClass.getBeforeClass() != null) {
testClass.getBeforeClass().invoke(object, new Object[0]);
}
List tests = testClass.getTests();
for (Method method : tests) {
try {
if (testClass.getBefore() != null) {
testClass.getBefore().invoke(object, new Object[0]);
}
try {
method.invoke(object, new Object[0]);
System.out.println("Method : [ "+ method.getName()+" ] PASS " );
} catch (Exception ex) {
System.out.println("Method : [ "+ method.getName()+" ] ERROR " );
}
} finally {
if (testClass.getAfter() != null) {
testClass.getAfter().invoke(object, new Object[0]);
}
}
}
} finally {
if (testClass.getAfterClass() != null) {
testClass.getAfterClass().invoke(object, new Object[0]);
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println("___________________________________________________________________________");
}
for (Class clazz : testClazzs) {testAll loops all registered test.
try {
System.out.println("CLASS : ["+clazz.getName()+"]");
Test inspectClass = EClassUtils.inspectClass(clazz);
EClassUtils.testClass(inspectClass, clazz.newInstance());
} catch (Exception ex) {
ex.printStackTrace();
}
}
public void testAll() {
Set> entrySet = bundles.entrySet();
System.out.println("===========================================================================");
for (Entry entry : entrySet) {
test(entry.getKey());
}
System.out.println("============================================================================");
}
IV- Console
to give the user opportunities to test specific fragment or do all test our activator register CommandProvider service with 3 methods starting (_) :public Object _test(CommandInterpreter intp) {(and _helpTest and not -help to conserve equinox help too)
String nextArgument = intp.nextArgument();
testExtender.test(Long.parseLong(nextArgument));
return null;
}
public Object _testall(CommandInterpreter intp) {
testExtender.testAll();
return null;
}
public Object _helpTest(CommandInterpreter intp) {
String help = getHelp();
System.out.println(help);
return null;
}
@Override
public String getHelp() {
StringBuilder buffer = new StringBuilder();
buffer.append("---Testing commands---\n\t");
buffer.append("test [bundle id] - test bundle fragment id\n\t");
buffer.append("testall - test all fragments\n\t");
buffer.append("help - Print this help\n");
return buffer.toString();
}
V-Fragment
our fragment contains 2 class one is to be tested (OneTest):import static org.junit.Assert.*;with manifest headers :
public class OneTest {
@Test
public void echo() {
assertFalse(false);
}
@Test
public void fail() {
assertTrue(false);
}
}
Fragment-Host: com.jtunisie.osgi.clientOutput :
Unit-Test: true
30 ACTIVE com.jtunisie.osgi.test.extender_1.0.0.SNAPSHOT
31 ACTIVE com.jtunisie.osgi.client_1.0.0.SNAPSHOT
Fragments=32
32 RESOLVED com.jtunisie.osgi.fragment.test_1.0.0.SNAPSHOT
Master=31
osgi> test 32
Bundle : [32] : com.jtunisie.osgi.fragment.test
_
CLASS : [com.jtunisie.osgi.fragment.test.OneTest]
___________________________________________________________________________
Method : [ fail ] ERROR
Method : [ echo ] PASS
___________________________________________________________________________
conclusion :
Hope this is article help you to test your bundles using clean fragment. Project can be exported to 4.1 release using listeners but BundleTracker is very helpful .
source code is shared in kenai : http://kenai.com/projects/testosgifragment
unit test
Fragment (logic)
JUnit
Opinions expressed by DZone contributors are their own.
Comments