|
From: Bob N. <rn...@co...> - 2004-04-21 13:41:18
|
Hi,
I've been experimenting with the Spring Framework since November. I've
been using Spring in conjunction with JUnit in order to more easily wire
up my testcases. I've not see this approach mentioned on the mailing
lists, so I thought I'd share what I have (for reference, my test
application is 4000 lines of code and I have 94.1% code coverage by
Clover's measure, partly because this approach makes it much easier to
factor out common tests and apply them).
Basically, all of my TestCases's extend AbstractSpringEnabledTestCase
(this, in turn extends an AbstractTestCase which extends
junit.framework.TestCase, so that non-spring extensions can be added).
This class, as part of setUp() and tearDown() attempts to load XML files
with the same name as the testcases class and all of its parents. This
allowed me to easily wire up my test fixtures. As a very useful side
effect, this also persuaded me to factor out some of the commonalities
in my testcases to form a hierarchy.
Essentially, for each interface I had defined, I've ended up with an
Abstract<Interface>TestCase with a corresponding
Abstract<Interface>TestCase.xml with common wiring requirements. Where I
have multiple implementations of a given interface it is often only
necessary to create a concrete extension and simply inherit the test
suite and its configuration.
I'm interesting if anyone thinks this is a useful approach (or is it too
obvious?). I'm also interested if anyone thinks it's a bad idea!
I've appended the full source of AbstractSpringEnabledTestCase below. If
anyone wants a more complete example, I could spend some time on that.
A typical use of this class is to extend it, add a Spring configuration
file named after the new concrete test case, and then access the context
with the protected getContext() method.
Regards,
Robert Newson.
/*
* Created on 23-Nov-2003
*/
package com.connected.junit.spring;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.connected.junit.AbstractTestCase;
/**
* For a given TestCase, we will load the following Spring contexts;
* applicationContext.xml - the root context.
* <package>/<classname>.xml - The context for the test case.
* all parent classes of test case are also searched.
* @author rnewson
*/
public abstract class AbstractSpringEnabledTestCase extends
AbstractTestCase {
private AbstractApplicationContext context;
protected ApplicationContext getContext() {
return context;
}
protected void setUp() throws Exception {
super.setUp();
context = null;
List classHierarchy = getClassHierarchy();
for (int i = classHierarchy.size() - 1; i >= 0; i--) {
Class clazz = (Class) classHierarchy.get(i);
context =
getApplicationContext(
clazz,
getClassName(clazz) + ".xml",
context);
}
}
protected void tearDown() throws Exception {
if (context != null) {
context.close();
}
}
private List getClassHierarchy() {
List result = new ArrayList();
Class superclass = getClass();
do {
result.add(superclass);
} while ((superclass = superclass.getSuperclass()) != null);
return result;
}
private String getClassName(Class clazz) {
String fullClassName = clazz.getName();
String result =
fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
return result;
}
private AbstractApplicationContext getApplicationContext(
Class clazz,
String resourceName,
AbstractApplicationContext parentContext)
throws IOException {
URL url = clazz.getResource(resourceName);
if (url == null) {
return parentContext;
}
return new ClassPathXmlApplicationContext(
new String[] { url.toString()},
parentContext);
}
}
|
|
From: Colin S. <col...@ex...> - 2004-04-21 14:20:02
|
Hi Bob,
This is a useful approach and class. My only comment is that depending
on what is set up inside the ApplicationContext, the startup time for
creating the appcontext is long enough (even 5 seconds is pretty long)
that it may be overkill to set up the context for every single test,
especially given the fact that the context is relatively static and
contains singletons or produces new beans as prototypes. In that
respect, sharing an application context instance for all tests within a
TestCase can end up saving a lot of time across a whole project full of
TestCases. When you get into more 'integration' type tests that do
something like start up a Hibernate SessionFactory, it _really_ saves a
lot of time.
What I do to share an appcontext across a whole TestCase, is use some
helper classes from the junit extensions package, as follows:
...
// --- attributes
// per-TestSuite specific vars, via ResourceManager!
ApplicationContext appContext;
...
public static Test suite() {
return new TestSetup(new TestSuite(BasicMappedPersistenceTest.class)) {
protected void setUp() throws Exception {
try {
ApplicationContext appContext;
appContext = new ClassPathXmlApplicationContext(new String[]
{CONTEXT, MAPPER_CONTEXT});
ResourceManager.addResource(APP_CONTEXT_ID, appContext);
}
catch (RuntimeException t) {
// just for debugging since Eclipse swallows these
throw t;
}
catch (Error e) {
// just for debugging since Eclipse swallows these
throw e;
}
}
protected void tearDown() throws Exception {
ResourceManager.removeResource(APP_CONTEXT_ID);
}
};
}
protected void setUp() throws Exception {
appContext = (ApplicationContext)
ResourceManager.getResource(APP_CONTEXT_ID);
}
protected void tearDown() throws Exception {
appContext = null;
}
Essentially the junit extensions ResourceManager class allows you to
share certain object instances across the whole TestCase. This approach
would of course also probably work when assembling a test suite out of a
whole bunch of testcases.
Regards,
Colin
Bob Newson wrote:
> Hi,
>
> I've been experimenting with the Spring Framework since November. I've
> been using Spring in conjunction with JUnit in order to more easily
> wire up my testcases. I've not see this approach mentioned on the
> mailing lists, so I thought I'd share what I have (for reference, my
> test application is 4000 lines of code and I have 94.1% code coverage
> by Clover's measure, partly because this approach makes it much easier
> to factor out common tests and apply them).
>
> Basically, all of my TestCases's extend AbstractSpringEnabledTestCase
> (this, in turn extends an AbstractTestCase which extends
> junit.framework.TestCase, so that non-spring extensions can be added).
> This class, as part of setUp() and tearDown() attempts to load XML
> files with the same name as the testcases class and all of its
> parents. This allowed me to easily wire up my test fixtures. As a very
> useful side effect, this also persuaded me to factor out some of the
> commonalities in my testcases to form a hierarchy.
>
> Essentially, for each interface I had defined, I've ended up with an
> Abstract<Interface>TestCase with a corresponding
> Abstract<Interface>TestCase.xml with common wiring requirements. Where
> I have multiple implementations of a given interface it is often only
> necessary to create a concrete extension and simply inherit the test
> suite and its configuration.
>
> I'm interesting if anyone thinks this is a useful approach (or is it
> too obvious?). I'm also interested if anyone thinks it's a bad idea!
>
> I've appended the full source of AbstractSpringEnabledTestCase below.
> If anyone wants a more complete example, I could spend some time on that.
>
> A typical use of this class is to extend it, add a Spring
> configuration file named after the new concrete test case, and then
> access the context with the protected getContext() method.
>
> Regards,
> Robert Newson.
>
> /*
> * Created on 23-Nov-2003
> */
> package com.connected.junit.spring;
>
> import java.io.IOException;
> import java.net.URL;
> import java.util.ArrayList;
> import java.util.List;
>
> import org.springframework.context.support.AbstractApplicationContext;
> import
> org.springframework.context.support.ClassPathXmlApplicationContext;
>
> import com.connected.junit.AbstractTestCase;
>
> /**
> * For a given TestCase, we will load the following Spring contexts;
> * applicationContext.xml - the root context.
> * <package>/<classname>.xml - The context for the test case.
> * all parent classes of test case are also searched.
> * @author rnewson
> */
> public abstract class AbstractSpringEnabledTestCase extends
> AbstractTestCase {
>
> private AbstractApplicationContext context;
>
> protected ApplicationContext getContext() {
> return context;
> }
>
> protected void setUp() throws Exception {
> super.setUp();
>
> context = null;
> List classHierarchy = getClassHierarchy();
> for (int i = classHierarchy.size() - 1; i >= 0; i--) {
> Class clazz = (Class) classHierarchy.get(i);
> context =
> getApplicationContext(
> clazz,
> getClassName(clazz) + ".xml",
> context);
> }
> }
>
> protected void tearDown() throws Exception {
> if (context != null) {
> context.close();
> }
> }
>
> private List getClassHierarchy() {
> List result = new ArrayList();
> Class superclass = getClass();
> do {
> result.add(superclass);
> } while ((superclass = superclass.getSuperclass()) != null);
> return result;
> }
>
> private String getClassName(Class clazz) {
> String fullClassName = clazz.getName();
> String result =
> fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
> return result;
> }
>
> private AbstractApplicationContext getApplicationContext(
> Class clazz,
> String resourceName,
> AbstractApplicationContext parentContext)
> throws IOException {
> URL url = clazz.getResource(resourceName);
>
> if (url == null) {
> return parentContext;
> }
>
> return new ClassPathXmlApplicationContext(
> new String[] { url.toString()},
> parentContext);
> }
> }
|
|
From: Bob N. <rn...@co...> - 2004-04-21 14:47:16
|
Hi Colin,
That's a good point. Currently none of my contexts have that kind of
overhead, so I've not hit this in practise (yet!).
I confess I wasn't aware of the ResourceManager extension but my concern
with sharing the app context is one of test isolation. One thing I do to
get higher test coverage is to inject mock objects within the setUp() of
my concrete test classes (in order to throw IOException's, for example).
This would break tests if I were not tearing the context down each time.
I suppose the correct way is to configure the mock objects in the
context, of course. :)
I think a useful enhancement to my class is to make it easier to specify
test-specific configuration, rather than testcase-specific. This would
then let me reuse existing application contexts for the higher levels. I
could also then remove my programmatic configuration of beans for those
cases where I test failure states.
B.
Colin Sampaleanu wrote:
> Hi Bob,
>
> This is a useful approach and class. My only comment is that depending
> on what is set up inside the ApplicationContext, the startup time for
> creating the appcontext is long enough (even 5 seconds is pretty long)
> that it may be overkill to set up the context for every single test,
> especially given the fact that the context is relatively static and
> contains singletons or produces new beans as prototypes. In that
> respect, sharing an application context instance for all tests within a
> TestCase can end up saving a lot of time across a whole project full of
> TestCases. When you get into more 'integration' type tests that do
> something like start up a Hibernate SessionFactory, it _really_ saves a
> lot of time.
>
> What I do to share an appcontext across a whole TestCase, is use some
> helper classes from the junit extensions package, as follows:
>
> ...
> // --- attributes
> // per-TestSuite specific vars, via ResourceManager!
> ApplicationContext appContext;
> ...
>
> public static Test suite() {
> return new TestSetup(new TestSuite(BasicMappedPersistenceTest.class)) {
>
> protected void setUp() throws Exception {
> try {
> ApplicationContext appContext;
> appContext = new ClassPathXmlApplicationContext(new String[]
> {CONTEXT, MAPPER_CONTEXT});
> ResourceManager.addResource(APP_CONTEXT_ID, appContext);
> }
> catch (RuntimeException t) {
> // just for debugging since Eclipse swallows these
> throw t;
> }
> catch (Error e) {
> // just for debugging since Eclipse swallows these
> throw e;
> }
> }
>
> protected void tearDown() throws Exception {
> ResourceManager.removeResource(APP_CONTEXT_ID);
> }
> };
> }
>
> protected void setUp() throws Exception {
> appContext = (ApplicationContext)
> ResourceManager.getResource(APP_CONTEXT_ID);
> }
> protected void tearDown() throws Exception {
> appContext = null;
> }
>
>
> Essentially the junit extensions ResourceManager class allows you to
> share certain object instances across the whole TestCase. This approach
> would of course also probably work when assembling a test suite out of a
> whole bunch of testcases.
>
> Regards,
> Colin
>
>
> Bob Newson wrote:
>
>> Hi,
>>
>> I've been experimenting with the Spring Framework since November. I've
>> been using Spring in conjunction with JUnit in order to more easily
>> wire up my testcases. I've not see this approach mentioned on the
>> mailing lists, so I thought I'd share what I have (for reference, my
>> test application is 4000 lines of code and I have 94.1% code coverage
>> by Clover's measure, partly because this approach makes it much easier
>> to factor out common tests and apply them).
>>
>> Basically, all of my TestCases's extend AbstractSpringEnabledTestCase
>> (this, in turn extends an AbstractTestCase which extends
>> junit.framework.TestCase, so that non-spring extensions can be added).
>> This class, as part of setUp() and tearDown() attempts to load XML
>> files with the same name as the testcases class and all of its
>> parents. This allowed me to easily wire up my test fixtures. As a very
>> useful side effect, this also persuaded me to factor out some of the
>> commonalities in my testcases to form a hierarchy.
>>
>> Essentially, for each interface I had defined, I've ended up with an
>> Abstract<Interface>TestCase with a corresponding
>> Abstract<Interface>TestCase.xml with common wiring requirements. Where
>> I have multiple implementations of a given interface it is often only
>> necessary to create a concrete extension and simply inherit the test
>> suite and its configuration.
>>
>> I'm interesting if anyone thinks this is a useful approach (or is it
>> too obvious?). I'm also interested if anyone thinks it's a bad idea!
>>
>> I've appended the full source of AbstractSpringEnabledTestCase below.
>> If anyone wants a more complete example, I could spend some time on that.
>>
>> A typical use of this class is to extend it, add a Spring
>> configuration file named after the new concrete test case, and then
>> access the context with the protected getContext() method.
>>
>> Regards,
>> Robert Newson.
>>
>> /*
>> * Created on 23-Nov-2003
>> */
>> package com.connected.junit.spring;
>>
>> import java.io.IOException;
>> import java.net.URL;
>> import java.util.ArrayList;
>> import java.util.List;
>>
>> import org.springframework.context.support.AbstractApplicationContext;
>> import
>> org.springframework.context.support.ClassPathXmlApplicationContext;
>>
>> import com.connected.junit.AbstractTestCase;
>>
>> /**
>> * For a given TestCase, we will load the following Spring contexts;
>> * applicationContext.xml - the root context.
>> * <package>/<classname>.xml - The context for the test case.
>> * all parent classes of test case are also searched.
>> * @author rnewson
>> */
>> public abstract class AbstractSpringEnabledTestCase extends
>> AbstractTestCase {
>>
>> private AbstractApplicationContext context;
>> protected ApplicationContext getContext() {
>> return context;
>> }
>>
>> protected void setUp() throws Exception {
>> super.setUp();
>>
>> context = null;
>> List classHierarchy = getClassHierarchy();
>> for (int i = classHierarchy.size() - 1; i >= 0; i--) {
>> Class clazz = (Class) classHierarchy.get(i);
>> context =
>> getApplicationContext(
>> clazz,
>> getClassName(clazz) + ".xml",
>> context);
>> }
>> }
>>
>> protected void tearDown() throws Exception {
>> if (context != null) {
>> context.close();
>> }
>> }
>>
>> private List getClassHierarchy() {
>> List result = new ArrayList();
>> Class superclass = getClass();
>> do {
>> result.add(superclass);
>> } while ((superclass = superclass.getSuperclass()) != null);
>> return result;
>> }
>>
>> private String getClassName(Class clazz) {
>> String fullClassName = clazz.getName();
>> String result =
>> fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
>> return result;
>> }
>>
>> private AbstractApplicationContext getApplicationContext(
>> Class clazz,
>> String resourceName,
>> AbstractApplicationContext parentContext)
>> throws IOException {
>> URL url = clazz.getResource(resourceName);
>>
>> if (url == null) {
>> return parentContext;
>> }
>>
>> return new ClassPathXmlApplicationContext(
>> new String[] { url.toString()},
>> parentContext);
>> }
>> }
>
>
>
>
>
> -------------------------------------------------------
> This SF.Net email is sponsored by: IBM Linux Tutorials
> Free Linux tutorial presented by Daniel Robbins, President and CEO of
> GenToo technologies. Learn everything from fundamentals to system
> administration.http://ads.osdn.com/?ad_id=1470&alloc_id=3638&op=click
|