Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Feedback on gradle configuration for `test-support` code

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.

  1. 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?

  2. 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?

  3. 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.

like image 388
Thomas Eizinger Avatar asked Nov 15 '16 01:11

Thomas Eizinger


1 Answers

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
    }
}
like image 158
Marwin Avatar answered Nov 05 '22 03:11

Marwin