I prefer putting the test classes into the same package as the project classes they test, but in a different physical directory, like:
myproject/src/com/foo/Bar.java
myproject/test/com/foo/BarTest.java
In a Maven project it would look like this:
myproject/src/main/java/com/foo/Bar.java
myproject/src/test/java/com/foo/BarTest.java
The main point in this is that my test classes can access (and test!) package-scope classes and members.
As the above example shows, my test classes have the name of the tested class plus Test
as a suffix. This helps finding them quickly - it's not very funny to try searching among a couple of hundred test classes, each of whose name starts with Test
...
Update inspired by @Ricket's comment: this way test classes (typically) show up right after their tested buddy in a project-wise alphabetic listing of class names. (Funny that I am benefiting from this day by day, without having consciously realized how...)
Update2: A lot of developers (including myself) like Maven, but there seems to be at least as many who don't. IMHO it is very useful for "mainstream" Java projects (I would put about 90% of projects into this category... but the other 10% is still a sizeable minority). It is easy to use if one can accept the Maven conventions; however if not, it makes life a miserable struggle. Maven seems to be difficult to comprehend for many people socialized on Ant, as it apparently requires a very different way of thinking. (Myself, having never used Ant, can't compare the two.) One thing is for sure: it makes unit (and integration) testing a natural, first-class step in the process, which helps developers adopt this essential practice.
You can split them very easily using JUnit categories and Maven.
This is shown very, very briefly below by splitting unit and integration tests.
Define A Marker Interface
The first step in grouping a test using categories is to create a marker interface.
This interface will be used to mark all of the tests that you want to be run as integration tests.
public interface IntegrationTest {}
Mark your test classes
Add the category annotation to the top of your test class. It takes the name of your new interface.
import org.junit.experimental.categories.Category;
@Category(IntegrationTest.class)
public class ExampleIntegrationTest{
@Test
public void longRunningServiceTest() throws Exception {
}
}
Configure Maven Unit Tests
The beauty of this solution is that nothing really changes for the unit test side of things.
We simply add some configuration to the maven surefire plugin to make it to ignore any integration tests.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.11</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit47</artifactId>
<version>2.12</version>
</dependency>
</dependencies>
<configuration>
<includes>
<include>**/*.class</include>
</includes>
<excludedGroups>com.test.annotation.type.IntegrationTest</excludedGroups>
</configuration>
</plugin>
When you do a mvn clean test only your unmarked unit tests will run.
Configure Maven Integration Tests
Again the configuration for this is very simple.
To run only the integration tests, use this:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.11</version>
<dependencies>
<dependency>
<groupId>org.apache.maven.surefire</groupId>
<artifactId>surefire-junit47</artifactId>
<version>2.12</version>
</dependency>
</dependencies>
<configuration>
<groups>com.test.annotation.type.IntegrationTest</groups>
</configuration>
</plugin>
If you wrap this in a profile with id IT
, you can run only the fast tests using mvn clean install
. To run just the integration/slow tests, use mvn clean install -P IT
.
But most often, you will want to run the fast tests by default and all tests with -P IT
. If that's the case, then you have to use a trick:
<profiles>
<profile>
<id>IT</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludedGroups>java.io.Serializable</excludedGroups> <!-- An empty element doesn't overwrite, so I'm using an interface here which no one will ever use -->
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
As you can see, I'm excluding tests that are annotated with java.io.Serializable
. This is necessary because the profile will inherit the default config of the Surefire plugin, so even if you say <excludedGroups/>
or <excludedGroups></excludedGroups>
, the value com.test.annotation.type.IntegrationTest
will be used.
You also can't use none
since it has to be an interface on the classpath (Maven will check this).
Notes:
- The dependency to
surefire-junit47
is only necessary when Maven doesn't switch to the JUnit 4 runner automatically. Using the groups
or excludedGroups
element should trigger the switch. See here.
- Most of the code above was taken from the documentation for the Maven Failsafe plugin. See the section "Using JUnit Categories" on this page.
- During my tests, I found that this even works when you use
@RunWith()
annotations to run suites or Spring-based tests.
Best Answer
Yes, this is perfectly possible. All you have to do is to use the same
locations
attribute in your test classes:Spring caches application contexts by
locations
attribute so if the samelocations
appears for the second time, Spring uses the same context rather than creating a new one.I wrote an article about this feature: Speeding up Spring integration tests. Also it is described in details in Spring documentation: 9.3.2.1 Context management and caching.
This has an interesting implication. Because Spring does not know when JUnit is done, it caches all context forever and closes them using JVM shutdown hook. This behavior (especially when you have a lot of test classes with different
locations
) might lead to excessive memory usage, memory leaks, etc. Another advantage of caching context.