gradle-clover-plugin to get code coverage from separate module - java

I was tasked with splitting up a spring boot application to have multiple modules to look something like this:
root
app
build.gradle
rest
build.gradle
service
build.gradle
dao
build.gradle
model
build.gradle
build.gradle
settings.gradle
Note: there is no code folder in the root of the project. Each module has its own src root
Before splitting the code into modules, the gradle-clover-plugin was showing that we had 95% code coverage, but due to the way that the tests are now organized, the plugin is not able to see that some of the code is being tested in other modules
For instance some of the service Class methods are being tested from the rest module while testing the controllers.
Looking thru the documentation for the plugin (https://github.com/bmuschko/gradle-clover-plugin/blob/master/README.md), they mention specifying additionalSourceSets and additionalTestSourceSets in the example towards the bottom of the page, but it's not clear to me how to use those in my project
I added the following block from the example to my root build.gradle:
additionalSourceSet {
srcDirs = sourceSets.generatedCode.java.srcDirs
classesDir = sourceSets.generatedCode.java.outputDir
}
additionalTestSourceSet {
srcDirs = sourceSets.integrationTest.java.srcDirs
classesDir = sourceSets.integrationTest.java.outputDir
}
but was getting the following exception: Could not get unknown property 'generatedCode' for SourceSet container of type org.gradle.api.internal.tasks.DefaultSourceSetContainer.
I then tried swapping out generatedCode and integrationTest with the names of my modules in the root build.gradle but was getting the same exception
Finally I tried specifying those properties in the build.gradle for the module, but was getting the following exception: Could not find method additionalSourceSets() for arguments on object of type com.bmuschko.gradle.clover.CloverPluginConvention
Is it indeed possible to specify another modules' sources for the plugin to consider when generating the code coverage report for a different module?

Related

Gradle nested projects setup

How do I set up such a project structure where I have the root project called "app", "frontend" and "backend" project inside and a count of library projects inside each. Then run a build task that would give me one backend jar and a swing (for example) jar application.
Like this:
root (App)
frontend
library
main
backend
library
main
then run: gradle build and have build/.../frontend.jar and build/.../backend.jar
I did try using include inside settings.gradle but that doesn't seem to work (at least gradle projects and intellij idea do not recognise the projects inside frontend and backend). I had:
root/settings.gradle with: include 'frontend', 'backend'
root/backend/settings.gradle with: include 'library', 'main'
and the same for frontend
There are multiple ways. one way at the root settings.gradle (gradle v7.1)
rootProject.name = 'test-prj-tree'
include 'backend'
include 'frontend'
include 'library'
include 'backend:library'
findProject(':backend:library')?.name = 'backend-lib'
include 'backend:main'
findProject(':backend:main')?.name = 'backend-main'
include 'frontend:library'
findProject(':frontend:library')?.name = 'frontend-lib'
include 'frontend:main'
findProject(':frontend:main')?.name = 'frontend-main'
In modern Gradle it's possible to includeBuild, which solves the problem perfeclty:
// root/settings.gradle.kts
rootProject.name = "root"
includeBuild("backend")
includeBuild("frontend")
// root/backend/settings.gradle.kts and root/frontend/settings.gradle.kts
include(":library")
include(":main")

Gradle can't access classes defined in module src/main from src/test with JavaFX plugin

I am trying to allow my test classes to access the main classes (in a standard gradle setup). It was working fine until I put my main classes in a module (for JavaFX), at which point all tests stopped working. The main code runs fine.
If I understand correctly, according to this gradle documentation, doing nothing should run the tests normally, but I get an error:
org.gradle.api.internal.tasks.testing.TestSuiteExecutionException: Could not complete execution for Gradle Test Executor 1.
...
Caused by: java.lang.IllegalAccessError: class org.junit.platform.launcher.core.LauncherFactory (in unnamed module #0x50650eec) cannot access class org.junit.platform.commons.util.Preconditions (in module org.junit.platform.commons) because module org.junit.platform.commons does not export org.junit.platform.commons.util to unnamed module #0x50650eec
This happens if I use the intellij configuration, or run ./gradlew test.
So I attempted to fix the issue by patching the module-info, but then I get hundreds of errors like this one:
error: cannot find symbol
import snake.Snake
^
symbol: class Snake
location: package snake
Which IntelliJ explains with Package 'snake' is declared in module 'snakegame', which does not export it to module 'snakegame'. I'm guessing that it's referring to the original module-info.java which is defined in src/main/java, and the secondary module-info.java in src/test/java.
In the Gradle documentation it had a code snippet for adding the patch argument yourself in the build.gradle, however this just results in this error:
> java.lang.IllegalArgumentException: error: --patch-module specified more than once for module snakegame
The command which was actually executed looks like this:
compiler args for task compileTestJava: [--patch-module, snakegame=/project/path/snake/build/classes/java/main, --module-path, ... , --add-modules, org.junit.jupiter.api, --add-reads, snakegame=org.junit.jupiter.api, --patch-module, snakegame=/project/path/snake/src/test/java]
So it's patching two different modules, which I don't understand. It doesn't really matter to me how, I just need the classes to be runnable and have access to the main classes.
UPDATE:
So I recreated the project to try to create a minimal reproducible example and added in elements one at a time, and the problem didn't show up until I added in the gradle JavaFX plugin, at which point it stopped working.
So I started with the default structure, simply created a package called example with an Example.class, and created the same package in test, with a test class Example_Test.class with one test that creates an Example object. I then added an empty module-info in main. Original build.gradle:
plugins {
id 'java'
}
// default stuff
So at this point, everything is working fine. Then I change this to:
plugins {
id 'java'
id 'org.openjfx.javafxplugin' version '0.0.9'
}
And everything stops working
My preferred solution would be to use a test module-info.java or module-info.test, but I was not able to get this to work. I ended up simply ignoring modules for tests, which is a terrible workaround, but works for the moment. To ignore modules during testing, add this to the build.gradle:
plugins {
id 'java'
id 'org.openjfx.javafxplugin' version '0.0.9'
// other plugins
}
// other stuff
test {
useJUnitPlatform()
moduleOptions {
runOnClasspath = true
}
}
Set moduleOptions { runOnClasspath = true } in test
Useful links:
https://stackoverflow.com/a/58854685/12393574
https://github.com/junit-team/junit5/issues/2111#issuecomment-557925312
https://sormuras.github.io/blog/2018-09-11-testing-in-the-modular-world.html
https://stackoverflow.com/a/58990250/12393574
The JavaFX plugin adds the gradle-modules-plugin

Gradle test fixtures plugin and core module dependencies

I have a project built with Gradle version 6.4 and JDK 8. I'm trying to use the Gradle plugin for Test Fixtures (java-test-fixtures) but I have some issues with the dependencies.
According to the Gradle page linked above, the project should be structured like this:
core-module
-- src
-- main
-- java
-- test
-- java
-- testFixtures
-- java
While the build.gradle.kts file has the following dependencies section:
dependencies {
api("com.my.external.project:1.0")
// ... more API dependencies
testFixturesCompileOnly(project(":core-module"))
testFixturesApi("junit:junit:4.12")
// ... more test dependencies
}
Now, in IntelliJ (the IDE I'm using) classes in the testFixtures/java source folder see the classes in the main/java source folder. So I can add new Java classes under testFixtures/java that have dependencies on those under main.
However, I won't be able to import the dependencies from the external library com.my.external.project:1.0. The problem is confirmed when I try to run the Gradle task compileTestFixturesJava.
I can duplicate the entry in the dependencies section; e.g. I can add:
testFixturesImplementationOnly("com.my.external.project:1.0")
But that is not really what I expect to do; especially when I have dozens of dependencies.
I could also define the dependencies in an array and run a for-each over them. Still, this is not the cleanest solution.
Is there a clean solution that will allow the testFixtures module to use the dependencies declared in the main module?
Most important concept in the Gradle java-test-fixtures plugin is stated in their documentation:
[this plugin] will automatically create a testFixtures source set, in which you can write your test fixtures. Test fixtures are configured so that:
they can see the main source set classes
test sources can see the test fixtures classes
This plugin will indeed create the following dependencies: main <-- testFixtures , and testFixtures <-- test
In your case, testFixtures module should automatically depend on main sources, and also on main dependencies declared in api scope ( com.my.extenal.project:1.0)
See a similar example in a valid sample project here https://github.com/mricciuti/so-64133013 :
Simpsons class has access to Person class from main module
TestHelpers class has access to main dependencies declared in api configuration
Note that testFixtures will not inherit dependencies from the test module: if you need to use such libraries in this module (eg. JUnit, Mockito, ...) you will need to declare explicit dependency , using testFixturesImplementation or testFixturesApi configuration.
See example in core-module
plugins {
id ("java-library")
id ("java-test-fixtures")
}
dependencies {
// Main dependencies
// will be available in "testFixture" as well thanks to "api" configuration
api("org.apache.commons:commons-lang3:3.9")
api(project(":utils-module"))
// Testfixture dependencies
// ==> mockito lib will be available in testFixture module and also in consumer modules (e.g test)
testFixturesApi("org.mockito:mockito-core:3.5.13")
// Test dependencies
// dependencies specific to the "test" module; not visible from "testFixtures"
testImplementation("org.junit.jupiter:junit-jupiter-api:5.3.1")
testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:5.3.1")
}

Added Gradle to Java project "Exception ... java.lang.NoClassDefFoundError"

I had an existing project without Gradle and needed to add com.google.code.gson:gson:+ library to work with JSON objects. To begin with I ran either gradle init or gradle build, I'm not sure. This caused my java classes with a main() not to run as the source path was wrong/changed. I have changed the structure following advice to at least get the classes to compile and run, but I still have this warning in run configurations "Warning: Class 'Main' not found in module 'src'" ;
If I set Use classpath of module to src.main, the warning goes away but when I run Main.main() Gradle seems to execute Gradle tasks, like this - this will run indefinitely;
Here is my project structure;
This is my build.gradle file;
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java project to get you started.
* For more details take a look at the Java Quickstart chapter in the Gradle
* User Manual available at https://docs.gradle.org/6.3/userguide/tutorial_java_projects.html
*/
plugins {
// Apply the java plugin to add support for Java
id 'java'
// Apply the application plugin to add support for building a CLI application.
id 'application'
// idea plugin? // I added this to original build.gradle file
id 'idea'
}
repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
jcenter()
mavenCentral()
google()
}
dependencies {
// This dependency is used by the application.
implementation 'com.google.guava:guava:28.2-jre'
// Use JUnit test framework
testImplementation 'junit:junit:4.12'
// For use with JSONUtil class // I added this to original build.gradle file
compile 'com.google.code.gson:gson:+'
}
application {
// Define the main class for the application.
mainClassName = 'java.Main' // changed to 'Main' and I can `gradle run` seems to actually run Main.java
}
I have imported com.google.gson.JsonObject and com.google.gson.JsonParser from com.google.gson:gson:2.8.6 library, with no code inspection warnings, i.e available at compile time. If I run my code with a JsonObject jsonObject = new JsonObject I get the error;
Exception in thread "main" java.lang.NoClassDefFoundError: com/google/gson/JsonParser
at HttpUtils.getAccessToken(HttpUtils.java:80)
at Main.auth(Main.java:75)
at Main.play(Main.java:36)
at Main.main(Main.java:17)
Caused by: java.lang.ClassNotFoundException: com.google.gson.JsonParser
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 4 more
Line 80 of HttpUtils.java;
JsonObject jsonResponse = JsonParser.parseString(response.body()).getAsJsonObject(); // todo: status 200 "success" else failed
accessToken = jsonResponse.get("access_token").getAsString();
System.out.println(accessToken);
I understand this means that JVM can't compile a .class for JsonParser? I suppose this means the compiler has no knowledge of the library existing, which makes me suspect that Gradle isn't configured properly with the project, as it has downloaded the library, but not added a path to it?
I have tried gradle cleanIdea and then gradle idea. I have rebuilt the the project. I have "Mark directory as source root" on various directories for testing being careful to revert when it failed to change behaviour.
Edit;
I have added a package com.example in the src.main.Java directory and added the java files.
I edited run configuration for Main.java to
Main class: com.example.Main
Use classpath of module: src.main
I also changed the build.gradle file to;
application {
// Define the main class for the application.
mainClassName = 'com.example.Main'
}
Main runs but I am stuck at this point, which seems to run indefinitely;
Also, I am sure I right clicked on build.gradle and selected import, although I can't recreate this as the option isn't available now.
Edit 2;
I have been able to get the classes Main and Test with main() to run by putting them in the test/java/src package, and using unusual run configuration with warnings. Although on closer inspection, it seems to be running code that is previously compiled somewhere, as any changes I make aren't reflected in output.
Here is my project structure at the moment;
This is my run configuration that actually runs main in the standard output console, rather than a Gradle Task. It's clearly wrong, as Main is not in the com.example package or src.main module. If I set it correctly using module src.test and main class src.Main Gradle runs as screenshot 5.
Edit 3;
I see now that Gradle has took over responsibility to build and run the java files. I didn't know running in the output could be done with another CLI app and I admit it confused me, so please forgive anything above that seems stupid, I'm learning and figuring this out as I go.
I found in InteliJ settings Build, Execution, Deployment > Build Tools > Gradle I can change the Build and run using option between InteliJ IDEA and Gradle. The only issue I'm having with Gradle now I understand what is happening is Gradle doesn't seem to update my .class files when I run my main() with Gradle. Maybe this is for another question though.
mainClassName = 'java.Main' // changed to 'Main' and I can "gradle run" seems to actually run Main.java
This is not correct. Based on screenshot - you have not package named java (also I doubld that this is a valid name for a Java package). Create proper package inside src/main/java directory and specify it in the Main source file and in build.gradle file.
Also make sure you have imported build.gradle file in IDE, see Link a Gradle project to an IntelliJ IDEA project

IntelliJ / Gradle - composite build project - how to import from different module?

I'm new to Java and am currently trying to build a cucumber / selenium project in IntelliJ that contains two modules: A library project containing page definitions, and a test project that contains the cucumber features and step definitions that talk to those page definitions. The idea is that the page definitions are a shared resource, and the tests are specific to different projects / groups. Both modules are at the same level underneath the parent project. The build is using Gradle, and the settings.gradle file for the parent looks as follows:
rootProject.name = 'composite-builds'
includeBuild 'libraryproject'
includeBuild 'testproject'
Using Gradle includeBuild on the parent project works fine and the whole project imports. However I am having no luck using the library project in my import statements in the test project. It consistently returns me these kinds of error: java: package libraryproject.pageFactory.examplePages does not exist and is clearly not seeing the library module.
What do I need to do / add in order for the test project to recognise the library project? I did try to also add the includeBuild statement in the settings.gradle for the test project but this made no difference.
The library can be found here
Update: the real reason that I cannot see the modules from the library project is that they were held in the test folder, not main.
Go to your build.gradle file
Instead of includeBuild use dependencies{compile{project(':libraryproject')}}
Inside the Root Project of libraryproject which is in your case the composite-builds. Change includeBuild to include in the settings.gradle
rootProject.name = 'composite-builds'
include ':libraryproject'
include ':testproject'
If it is in the same root:
dependencies {
compile(
project(':libraryproject')
)
}
Subfolder:
dependencies {
compile(
project(':myFolder1:myFolder2:libraryproject')
)
}

Categories

Resources