I have been thinking about this a lot lately and wanted to get some feedback on the idea I had a couple of days ago.
Problem:
In a typical code base, every module has a main
and a test
source-set. This can work quite well for some time but sooner or later I always stumble upon the situation where I would like to group together a bunch of classes which allow easier testing of code that involves a certain module. A good example would be a set of hamcrest
matcher classes for a given module.
Assumption 1:
As hamcrest
is a library for test-code, these matchers should not go into the main
source-set.
Assumption 2:
These classes should also not go into the test
source-set either, as the dependency on the test
source is just a workaround for these classes to be available. One usually does not want a dependency on the actual tests.
It is also not recommended (by Netflix) to define a dependency on the test
source-set of a project.
Solution 1:
Create a dedicated module that contains these classes in the main
source-set and simply define a test-dependency on this module wherever you need them.
This was the approach I went with for quite some time now but I don't really like it.
First, I never came up with a nice name, except appending testSupport
to the name of the original module which results in names like core-testSupport
, persistence-testSupport
and so on.
Second, it creates a lot of modules and the project-tree gets kind of polluted with these modules.
Solution 2: (The one I would appreciate feedback on)
configurations {
testSupportCompile.extendsFrom compile
testSupportRuntime.extendsFrom runtime
}
sourceSets {
testSupport {
compileClasspath += sourceSets.main.output + configurations.testSupportCompile
runtimeClasspath += compileClasspath + configurations.testSupportRuntime
}
}
task testSupportJar(type: Jar) {
from sourceSets.testSupport.output
classifier 'testSupport'
}
artifacts {
testSupportCompile testSupportJar
}
The above gradle configuration could go in file named testSupport.gradle
and be applied to any module that needs this dedicated source-set for providing classes that could be reused in tests.
Defining a dependency would work like this:
testCompile project(path: ':core', configuration: 'testSupportCompile')
I am still kind of new to gradle and researched a lot but I still have a few questions.
I understand that declaring a new source-set automatically creates two configurations: <sourceSet>Compile
and <sourceSet>Runtime
. What I don't really like about this approach is, that one has to use the testSupportCompile configuration when declaring the dependency. Is there a way to alias this to only testSupport
or something like that?
My project compiles fine at the moment. However, I am not sure if I am doing things the right way. How could this configuration be improved?
Are there any other ways of achieving the desired functionality? While researching, I did not really find much on this topic which makes me feel like that I am either using the wrong search terms or doing something stupid that simply should not be done.
I know that this is kind of a broad question but I am not sure on where to get proper feedback on stuff like that except here.
I have a similar situation and I've been postponing the solution for some time, using various hacks and work-arounds. Your question was a final incentive to look into it.
This is what I ended up with -- EDIT made in cooperation with Thomas:
configurations {
// create a new configuration and inherit everything from compile
testlib.extendsFrom compile
}
sourceSets {
testlib {
// We will at least need access to our main sourceSet and all dependencies that are declared for our configuration.
compileClasspath += sourceSets.main.output + configurations.testlib
}
}
task testlibJar(type: Jar) {
from sourceSets.testlib.output
classifier 'testlib'
}
artifacts {
testlib testlibJar // include the classes into the new configuration
archives testlibJar // optional: include the support JAR into "uploadArchives", so it may also be used in other projects
}
Then, in the depending module, simply use:
dependencies {
testCompile project(path: ':otherproject', configuration: 'testlib')
}
Note that the (empty) testlibCompile
and testlibRuntime
configurations are still created (as a result of introducing the new testlib
source set), but I believe it is safe to just ignore them.
Also, it is often a case that your project's own test
configuration needs to use the testlib
(project tests depend on the generic test support). In such a case, you may either add a dependency between two configurations of the same project:
testCompile project(path: ':myproject', configuration: 'testlib')
Or enhance the compile and runtime classpaths individually:
configurations {
testlib.extendsFrom compile
testCompile.extendsFrom testlib
}
sourceSets {
test {
compileClasspath += sourceSets.testlib.output
runtimeClasspath += sourceSets.testlib.output
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With