views:

268

answers:

5

I am trying to speed up the Integration tests in our environment. All our classes are autowired. In our applicationContext.xml file we have defined the following:

<context:annotation-config/>
<context:component-scan base-package="com.mycompany.framework"/>
<context:component-scan base-package="com.mycompany.service"/>
...additional directories

I have noticed that Spring is scanning all directories indicated above and then iterates over each bean and caches the properties of each one. (I went over the DEBUG messages from spring)

As a result, the following test takes about 14 seconds to run:

public class MyTest extends BaseSpringTest {
  @Test
  def void myTest(){
    println "test"
  }
}

Is there any way to lazy load the configuration? I tried adding default-lazy-init="true" but that didn't work.

Ideally, only the beans required for the test are instantiated.

thanks in advance.

Update: I should have stated this before, I do not want to have a context file for each test. I also do not think one context file for just the tests would work. (This test context file would end up including everything)

+1  A: 

This is the price you pay for auto-detection of components - it's slower. Even though your test only requires certain beans, your <context:component-scan> is much broader, and Spring will instantiate and initialise every bean it finds.

I suggest that you use a different beans file for your tests, one which only defines the beans necessary for the test itself, i.e. not using <context:component-scan>.

skaffman
So that the idea is having a separate application context(s) for test(s)...whenever I have a test1 class where I need to use another bean, I add it to the test1 context and not use component scanning. Or use component scanning and trying to create tests according to packages, which seems to be nonsense.
lisak
I thought of this but I was trying to avoid defining a custom context file per test file. We are using these tests for partial integration tests (they need to hit the DB).
Tihom
+1  A: 

Probably what you need is to refactor your config to use less autowiring. My approach is almost always wire the beans by name, trying to be explicit with the design but, at the same time, not being too verbose either, using autowiring when is clear that you are using it in order to hide minor details.

Addendum: If that is not enough and you are using junit, you may want to use a utility from the JUnit Addons project. The class DirectorySuiteBuilder dynamically builds up a test suite from a directory structure. So you can make something like

DirectorySuiteBuilder builder = new DirectorySuiteBuilder();
Test suite = builder.suite("project/tests");

Initializing the Spring context before this code, you can run all tests at once. However, if each test assume a "clean" Spring context, then you are probably lost.

Ither
The project is large enough that this wouldn't be really feasible. We have autowired mostly everything.
Tihom
+2  A: 

One approach is to skip the auto detection completely and either load up a separate context (with the components required for the test) or redefine your beans at runtime (prior to the test running).

This thread discusses redefinition of beans and a custom test class for doing this:

http://stackoverflow.com/questions/565334/spring-beans-redefinition-in-unit-test-environment

Jon
+1 for pointing out things that I didn't know about Spring tests
Ither
A: 

In this kind of situation, you will need to find a balance. On one hand, you would rightly want to run the tests in a shortest possible time to get the results quick. This is especially important when working in a team environment with continuous integration working. On the other hand, you would also rightly want to keep the configuration of tests as simple as possible so the maintenance of test suite would not become too cumbersome to be useful.

But at the end of the day, you will need to find your own balance and make a decision. I would recommend creating a few context configuration files for testing to group some tests so such a simple test would not take long time simply being configured by Spring, while keeping the number of configuration files to minimum you can manage.

tim_wonil
+5  A: 

If you really want to speed up your application context, disable your <component-scan and performs the following routine before running any test

Resource resource = new ClassPathResource(<PUT_XML_PATH_RIGHT_HERE>); // source.xml, for instance
InputStream in = resource.getInputStream();

Document document = new SAXReader().read(in);
Element root  = document.getRootElement();

/**
  * remove component-scanning
  */
for ( Iterator i = root.elementIterator(); i.hasNext(); ) {
    Element element = (Element) i.next();

    if(element.getNamespacePrefix().equals("context") && element.getName().equals("component-scan"))
        root.remove(element);
}

in.close();

ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
for (String source: new String[] {"com.mycompany.framework", "com.mycompany.service"}) {
    for (BeanDefinition bd: scanner.findCandidateComponents(source)) {
        root
        .addElement("bean")
        .addAttribute("class", bd.getBeanClassName());
    }
}

/**
  * creates a new xml file which will be used for testing
  */ 
XMLWriter output = new XMLWriter(new FileWriter(<SET_UP_DESTINATION_RIGHT_HERE>));
output.write(document);
output.close(); 

Besides that, enable <context:annotation-config/>

As you need to perform the routine above before running any test, you can create an abstract class where you can run the following

Set up a Java system property for testing environment as follows

-Doptimized-application-context=false

And

public abstract class Initializer {

    @BeforeClass
    public static void setUpOptimizedApplicationContextFile() {
        if(System.getProperty("optimized-application-context").equals("false")) {
            // do as shown above

            // and

            System.setProperty("optimized-application-context", "true"); 
        }

    }

}

Now, for each test class, just extends Initializer

Arthur Ronald F D Garcia
@mkoryak And enable **default-lazy-init="true"**
Arthur Ronald F D Garcia
We found that the scanning took about 4 seconds and creating the dependency tree took about 10 seconds. Will this reduce the scanning time or the dependency tree time or both?
Tihom
@Tihom You just need to run once. Nothing else. It will create its xml counterpart and avoid reflection overhead
Arthur Ronald F D Garcia
Very nice (+1).
Pascal Thivent