Make your JUnit Tests run faster when using Spring
JRoller is down, and has been down for an hour or so - so I've decided to post this Spring Live entry here.
I discovered an interesting thing today about Spring and my JUnit tests. I noticed that the VelocityEngine I was setting on my
PositionManager was getting initialized once for each test* method in my Test. This means that
since my PositionManagerTest has 10 test methods - it would load the context 10 times.
Loading the context so many times was because the following code was in my Test's parent's
constructor:
ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
I suppose I expected any constructor-iniatialized variables to be initialized once and only
once. So I figured out a solution to make my JUnit tests run faster. By making the ctx
variable static, and loading the file in the member variables definition, I
greatly reduced the amount of time needed to run tests. Below is the new code I'm using:
protected static ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");
By doing this, the ApplicationContext is only set once, and my tests run much faster. Here's some performance comparisons from Struts Resume:
Average time to run "ant test-dao": 36 seconds
Average time to run "ant test-dao" after this change: 26 seconds
A 10 second improvement - that's crazy talk dontcha think?! I've tried it on single tests, as well as suites - and it seems to improve performance by approximately 30% across the board.
Because of this experience, I have to recommend that when you write JUnit tests that use Spring - you should initialize your ApplicationContext in a static member variable. It seems to be the best performing and logical choice. Of course, if I'm off my rocker - please let me know.
On a sidenote, it would be cool if Roller allowed me to turn off comments for a single post. I like how Simon posts stuff on java.net and then aggregates it to his personal weblog.
Posted by Sam Newman on April 08, 2004 at 08:27 AM MDT #
Posted by Sam Newman on April 08, 2004 at 09:11 AM MDT #
Posted by wassup on April 08, 2004 at 09:36 AM MDT #
Posted by Matthew Schmidt on April 08, 2004 at 10:12 AM MDT #
Posted by Sam Newman on April 08, 2004 at 10:28 AM MDT #
Posted by Davide Baroncelli on April 08, 2004 at 12:20 PM MDT #
I agree the line between unit and integration/functional tests is not always a clear one. You could argue that Matt could Mock out the application context, however if you assume that all external tools are 'safe' then using Spring to produce the context is an acceptable time saving (from the point of view of writing the test). LEts put it another way - what if my test calls a utility class? On the one hand I'm effectively testing the utility class itself, but I tend to be pragmatic about such things. If the other classes I'm using are part of the same project I'll mock them out, if its an external API I'm more inclined to allow their use.
In the case of your Hibernate DAO's for example, in testing the DAO's themselves I would contact the database via Hibernate. If I was using the DAO in a different test I would mock them out rather than contacting the DB - if you assume that the DAO has loaded from the DB you put yourself in the situation of being unable to have several tests run and fail (which is possible if all tests are isolated) - if your DAO fails to load, tests which rely on them will also fail.
Posted by Sam Newman on April 08, 2004 at 12:55 PM MDT #
Posted by Haroon Rafique on April 08, 2004 at 01:42 PM MDT #
Posted by Davide Baroncelli on April 08, 2004 at 01:50 PM MDT #
Posted by Cedric on April 08, 2004 at 02:52 PM MDT #
Posted by Davide Baroncelli on April 08, 2004 at 03:12 PM MDT #
Posted by Sam Newman on April 08, 2004 at 03:15 PM MDT #
Posted by Davide Baroncelli on April 08, 2004 at 03:19 PM MDT #
Posted by Sam Newman on April 08, 2004 at 03:46 PM MDT #
Posted by Davide Baroncelli on April 08, 2004 at 05:12 PM MDT #
This feature, as others have said is by design. The problem stems from the fact that just because we have setUp() and tearDown() methods, what stops someone from instantiating an object in the constructor and sharing it in your test methods that way? When you do that, who cares if you have setUp() and tearDown() you've just bypassed their use/purpose. (That's what Matt actualy tried to do in his example). So, if you think about it, the design is actualy pretty simple and it works to enforce test isolation behaviour.
Of course you are then left with the problem Matt wrote about, which in the way the he solved it for this particular scenario is not a bad idea at all.
Personaly I would even avoid doing this as it is starting to look more like an integration test then a unit test.
Posted by John Cavacas on April 08, 2004 at 05:30 PM MDT #
The design I'm using (loading applicationContext.xml in setUp()) is actually something I got from Spring's PetClinic app. I (personally) feel it's important to use Spring's wiring abilities in my unit tests, rather than re-wiring them myself - or what am I testing? Method calls and return values? Those seem to simple to really need testing.
I agree that it would be cool to dynamically mock my dataSource, but it's really not that hard to use HSQL or MySQL to do the real thing. That being said - speeding up my unit tests is one of my primary concerns in the next few months. I'm sure mocking will help that. Therefore, my quest for knowledge continues.
It would be cool if you could somehow tell Spring to use a Mock programmatically in your unit test and have it wire that instead of what's in the applicationContext.xml. Maybe you can and I just need to be enlightened.
Posted by Matt Raible on April 08, 2004 at 06:10 PM MDT #
Posted by Francisco Hernandez on April 08, 2004 at 06:42 PM MDT #
Posted by essam on February 19, 2006 at 10:02 AM MST #
Posted by Ronny Næss on September 06, 2006 at 08:44 AM MDT #
The SpringTestCase The DatabaseTestCase The JdbcDaoTest I'm sure there is possible to optimise this a lot, but I think I will stop here and I have used way more time figuring how to pass this problem anyway.
Posted by Ronny Næss on September 07, 2006 at 10:44 AM MDT #