ArchUnit icon indicating copy to clipboard operation
ArchUnit copied to clipboard

Best practices for Maven multi-module + Junit + reusable tests

Open internetstaff opened this issue 2 years ago • 2 comments

We have a Maven multi-module project and have been using the Maven plugin for a while successfully: our rules are all in an arch module, and the plugin just configures the rule set in every other module.

We wanted to switch to using Junit tests in order to get Junit output from our CI pipelines.

We used the surefire dependenciesToScan option to pull in a set of @AnalyzeClasses / @ArchTest tests from the arch module, and that seems to work with one major caveat:

Every module tests all classes from every module it depends on. We end up redundantly testing many smaller modules instead of merely running one test pass per module. ImportOptions on @AnalyzeClasses doesn't help because these aren't archives / jars; the other module classes are simply on the classpath.

It seems like we might be able to drop back to using new ClassFileImporter().importPath(".") and @Test using RULE.check(classes), but this seems non-optimal and made me suspect we're missing something or we're trying to do something dumb. :)

Is there a recommended way of doing this?

Thanks!

internetstaff avatar Sep 22 '22 20:09 internetstaff

One thing I don't unterstand is how you configured the dependencies between your modules :thinking: Because the multi-module setups I know, all dependencies would in fact be archives, i.e. one module depending on another module would mean it's pulled in from .m2/repository/... on the classpath. So, when I try this with a little multi-module project, then doing

@AnalyzeClasses(importOptions = DoNotIncludeArchives.class)

is sufficient to ignore all the other classes from dependencies.

That said, you can achieve most things you would do with new ClassFileImporter().import... by using a custom LocationProvider.

Using that you could e.g. do something like

@AnalyzeClasses(locations = CustomLocationProvider.class)
public class SharedTest { ... }

public class CustomLocationProvider implements LocationProvider {
    @Override
    public Set<Location> get(Class<?> testClass) {
        return Collections.singleton(Location.of(Paths.get(".")));
    }
}

Given that Maven sets the working directory correctly (which AFAIK is the directory of the current module) this would likely also just test the class files that are really in your module. It would pretty much do the same as new ClassFileImporter().importPath("."), just using JUnit 5 support via @AnalyzeClasses. You just need to watch out with caching a little, because the cache expects any LocationProvider to return a constant result during the whole test suite run (i.e. if the path would change suddenly the default cache would likely not realize this). As long as you run each module's tests in a forked JVM this is no problem. If the tests of multiple modules share the same JVM you might have to set cacheMode = CacheMode.PER_CLASS (which might slow things down if you have multiple ArchUnit test classes that are supposed to test the same production classes).

codecholeric avatar Sep 24 '22 08:09 codecholeric

We're using multi-module without generating intermediate jar artifacts. We build containers and just let reactor pull in the referenced module dependencies instead of building jars we'd throw away.

Maybe we're weird? :)

Thanks for the suggestions!

internetstaff avatar Sep 26 '22 18:09 internetstaff