From cd814f2d544071848614dffe4a263e3e40776a0b Mon Sep 17 00:00:00 2001 From: Aiden fry Date: Fri, 5 Dec 2025 10:05:33 +0000 Subject: [PATCH 1/2] Add new WriteTestsTo annotation --- README.md | 152 ++++++++++++------ .../cover/annotations/WriteTestsTo.java | 57 +++++++ .../java/com/example/WriteTestsToExample.java | 40 +++++ 3 files changed, 200 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/diffblue/cover/annotations/WriteTestsTo.java create mode 100644 src/test/java/com/example/WriteTestsToExample.java diff --git a/README.md b/README.md index c4072f5..8667d85 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,15 @@ In turn this can be used by [Diffblue Cover](https://diffblue.com/cover) to tune ## Installation -Cover Annotations is published in the [Maven central repository](https://central.sonatype.com/artifact/com.diffblue.cover/cover-annotations/overview). -In order to use the annotations simply add `cover-annotations` as a dependency to your project, for example copying the snippet for Maven or Gradle from the repository page. +Cover Annotations is published in +the [Maven central repository](https://central.sonatype.com/artifact/com.diffblue.cover/cover-annotations/overview). +In order to use the annotations simply add `cover-annotations` as a dependency to your project, for example copying the +snippet for Maven or Gradle from the repository page. ### Maven -For installation into a Maven project the `provided` scope is recommended so that the annotations are available at compile and test time, but are not bundled with the project output: +For installation into a Maven project the `provided` scope is recommended so that the annotations are available at +compile and test time, but are not bundled with the project output: ``` @@ -32,7 +35,8 @@ For installation into a Maven project the `provided` scope is recommended so tha ### Gradle -For installation into a Gradle project the `compileOnly` and `testImplementation` configurations are recommended so that the annotations are available at compile and test time, but are not bundled with the project output: +For installation into a Gradle project the `compileOnly` and `testImplementation` configurations are recommended so that +the annotations are available at compile and test time, but are not bundled with the project output: ``` dependencies { @@ -45,11 +49,13 @@ dependencies { ## Usage Annotations placed on packages affect tests for all classes and methods under test in that package. -Annotations placed on classes affect tests for that class and all its methods under test, overriding package level annotations. +Annotations placed on classes affect tests for that class and all its methods under test, overriding package level +annotations. Annotations placed on methods affect just that method under test, overriding package and class level annotations. The annotations will be respected by Diffblue Cover via both command line and IntelliJ Plugin. -When used from the command line in conjunction with equivalent options then the command line options take priority over the annotations found. +When used from the command line in conjunction with equivalent options then the command line options take priority over +the annotations found. ### Mocking Annotations @@ -57,26 +63,28 @@ Mocking annotations allow fine grained control over what mocking should be prefe #### Using `@InTestsMock` -Perhaps you have a method that Diffblue Cover would ordinarily test using an `Integer` but you'd prefer to see it tested using `Mockito.mock(..)`. +Perhaps you have a method that Diffblue Cover would ordinarily test using an `Integer` but you'd prefer to see it tested +using `Mockito.mock(..)`. In this case you could annotate the method (or class, or package) to recommend mocking `Number`: ```java public class ClassUnderTest { - @InTestsMock(Number.class) - public static String methodUnderTest(Number number) { - return String.valueOf(number.intValue()); - } + @InTestsMock(Number.class) + public static String methodUnderTest(Number number) { + return String.valueOf(number.intValue()); + } } ``` -Conversely, if Diffblue Cover normally does mock a particular class, and you have a particular location where it shouldn't be then you can forbid it: +Conversely, if Diffblue Cover normally does mock a particular class, and you have a particular location where it +shouldn't be then you can forbid it: ```java -public class ClassUnderTest { - @InTestsMock(value = Number.class, decision = MockDecision.FORBIDDEN) - public static String methodUnderTest(Number number) { - return String.valueOf(number.intValue()); - } +public class ClassUnderTest { + @InTestsMock(value = Number.class, decision = MockDecision.FORBIDDEN) + public static String methodUnderTest(Number number) { + return String.valueOf(number.intValue()); + } } ``` @@ -89,20 +97,22 @@ public class ClassUnderTest { #### Using `@InTestsMockConstruction` -Perhaps you have a method that Diffblue Cover is unable to test, and you think it could make more progress using `Mockito.mockConstruction(Random.class)`. +Perhaps you have a method that Diffblue Cover is unable to test, and you think it could make more progress using +`Mockito.mockConstruction(Random.class)`. In this case you could annotate the method (or class, or package) to recommend mocking construction of `Random`: ```java public class ClassUnderTest { - @InTestsMockConstruction(Random.class) - public static int methodUnderTest() { - return new Random().nextInt(); - } + @InTestsMockConstruction(Random.class) + public static int methodUnderTest() { + return new Random().nextInt(); + } } ``` > [!NOTE] -> Note that using `@InTestsMockConstruction` has the same effect as, and can be overridden by, Cover CLI command line option: +> Note that using `@InTestsMockConstruction` has the same effect as, and can be overridden by, Cover CLI command line +> option: > > ``` > dcover create --mock-construction ClassToMockConstruction @@ -110,15 +120,16 @@ public class ClassUnderTest { #### Using `@InTestsMockStatic` -Perhaps you have a method that Diffblue Cover is unable to test, and you think it could make more progress using `Mockito.mockStatic(UUID.class)`. +Perhaps you have a method that Diffblue Cover is unable to test, and you think it could make more progress using +`Mockito.mockStatic(UUID.class)`. In this case you could annotate the method (or class, or package) to recommend mocking static methods of `UUID`: ```java public class ClassUnderTest { - @InTestsMockStatic(UUID.class) - public static Path methodUnderTest() { - return Paths.get(UUID.randomUUID() + ".zip"); - } + @InTestsMockStatic(UUID.class) + public static Path methodUnderTest() { + return Paths.get(UUID.randomUUID() + ".zip"); + } } ``` @@ -136,7 +147,8 @@ Custom input annotations allow particular inputs to be recommended to Diffblue C #### Using `@InTestsUseEnums` The `@InTestsUseEnums` annotation allows the user to recommend specific `enum` literal values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. ```java public static boolean isDateOrTimeBased(@InTestsUseEnums({"SECONDS", "YEARS", "FOREVER"}) ChronoUnit chronoUnit) { @@ -147,7 +159,8 @@ public static boolean isDateOrTimeBased(@InTestsUseEnums({"SECONDS", "YEARS", "F #### Using `@InTestsUseClasses` The `@InTestsUseClasses` annotation allows the user to recommend specific `Class` literal values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. For example the following method is annotated with an example class literal to achieve a positive test: ```java @@ -159,8 +172,10 @@ public static boolean isAnnotation(@InTestsUseClasses(Nullable.class) Class t #### Using `@InTestsUseStrings` The `@InTestsUseStrings` annotation allows the user to recommend specific `String` values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. -For example the following method is annotated with some genuine examples of song titles that can be used to achieve coverage: +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. +For example the following method is annotated with some genuine examples of song titles that can be used to achieve +coverage: ```java public static boolean isDayRelatedSongTitle(@InTestsUseStrings({"I Don't Like Mondays", "Here Comes The Weekend"}) String title) { @@ -174,10 +189,13 @@ public static boolean isDayRelatedSongTitle(@InTestsUseStrings({"I Don't Like Mo #### Using `@InTestsUseCharacters` The `@InTestsUseCharacters` annotation allows the user to recommend specific `char` values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. -For example the following method is annotated with a genuine examples characters that make up a Unicode surrogate pair that can be used to achieve a positive test: +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. +For example the following method is annotated with a genuine examples characters that make up a Unicode surrogate pair +that can be used to achieve a positive test: ```java + @Nullable public static Integer toNullableCodePoint( @InTestsUseCharacters('\uD801') char high, @@ -192,11 +210,12 @@ public static Integer toNullableCodePoint( #### Using `@InTestsUseBytes` The `@InTestsUseBytes` annotation allows the user to recommend specific `byte` values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. For example the following method is annotated to use a specific preferred value: ```java -public static String toUpperHexString(@InTestsUseBytes((byte)0xD1) byte input) { +public static String toUpperHexString(@InTestsUseBytes((byte) 0xD1) byte input) { return Long.toHexString(input).toUpperCase(); } ``` @@ -204,11 +223,12 @@ public static String toUpperHexString(@InTestsUseBytes((byte)0xD1) byte input) { #### Using `@InTestsUseShorts` The `@InTestsUseShorts` annotation allows the user to recommend specific `short` values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. For example the following method is annotated to use a specific preferred value: ```java -public static String toUpperHexString(@InTestsUseShorts((short)0xD1FF) short input) { +public static String toUpperHexString(@InTestsUseShorts((short) 0xD1FF) short input) { return Long.toHexString(input).toUpperCase(); } ``` @@ -216,7 +236,8 @@ public static String toUpperHexString(@InTestsUseShorts((short)0xD1FF) short inp #### Using `@InTestsUseIntegers` The `@InTestsUseIntegers` annotation allows the user to recommend specific `int` values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. For example the following method is annotated to use a specific preferred value: ```java @@ -228,7 +249,8 @@ public static String toUpperHexString(@InTestsUseIntegers(0xD1FFB) int input) { #### Using `@InTestsUseLongs` The `@InTestsUseLongs` annotation allows the user to recommend specific `long` values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. For example the following method is annotated to use a specific preferred value: ```java @@ -240,7 +262,8 @@ public static String toUpperHexString(@InTestsUseLongs(0xD1FFBL) long input) { #### Using `@InTestsUseFloats` The `@InTestsUseFloats` annotation allows the user to recommend specific `float` values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. For example the following method is annotated to use a specific preferred value: ```java @@ -252,7 +275,8 @@ public static boolean isNearPi(@InTestsUseFloats(3.14159f) float input) { #### Using `@InTestsUseDoubles` The `@InTestsUseDoubles` annotation allows the user to recommend specific `double` values to use in tests. -Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is unable to identify values to cover all cases. +Sometimes this can be useful to control the values used for cosmetic reasons, but it can also be useful when Cover is +unable to identify values to cover all cases. For example the following method is annotated to use a specific preferred value: ```java @@ -263,22 +287,25 @@ public static boolean isNearPi(@InTestsUseDoubles(Math.PI) float input) { ### Interesting Value Annotations -Interesting value annotations allow users to promote existing fields and methods to be identified and used in particular roles by Diffblue Cover when writing tests. +Interesting value annotations allow users to promote existing fields and methods to be identified and used in particular +roles by Diffblue Cover when writing tests. #### Using `@InterestingTestFactory` Indicates the annotated method as a useful factory method for use in tests. -Cover will automatically recognise factory methods that simply return a newly created instance, but may not identify more complicated factories. +Cover will automatically recognise factory methods that simply return a newly created instance, but may not identify +more complicated factories. This annotation allows such factory methods to be manually annotated so that Cover considers them for producing inputs. -For example the following method under test takes a `User` as input, but the `User` constructor is private and Cover doesn't naturally consider `ofStaff(String)` to be a safe factory method to call. -By annotating the `ofStaff(String)` with `@InterstingTestFactory` we can tell Cover that this should be considered a good factory method to use in tests. +For example the following method under test takes a `User` as input, but the `User` constructor is private and Cover +doesn't naturally consider `ofStaff(String)` to be a safe factory method to call. +By annotating the `ofStaff(String)` with `@InterstingTestFactory` we can tell Cover that this should be considered a +good factory method to use in tests. ```java public String getUserDisplayString(User user) { if (user.manager) { return user.username + " (manager)"; - } - else { + } else { return user.username; } } @@ -326,7 +353,7 @@ public class CarFactory { public static Car getFirstCar() { return INSTANCE.cars.get(0); } - + // and so on... } ``` @@ -343,6 +370,33 @@ public class CarPainter { } ``` +### Test Organization Annotations + +Test organization annotations allow users to control how tests are organized and structured. + +#### Using `@WriteTestsTo` + +The `@WriteTestsTo` annotation directs Diffblue Cover to write tests for a specific source class into a designated test +class file, rather than following the default naming template (configured via `--class-name-template`). + +The specified test class name must be alphanumeric, and the test class will be created in the test folder under the same +package structure as the source class. + +```java +package com.example.myapp; + +@WriteTestsTo("CustomTestClassName") +public class SourceClass { + public String getValue() { + return "example"; + } +} +// Tests will be written to: src/test/java/com/example/myapp/CustomTestClassName.java +``` + +> [!NOTE] +> This annotation can only be applied at the class level. + ### Experimental Annotations Experimental annotations should not be used in a production setting, but are diff --git a/src/main/java/com/diffblue/cover/annotations/WriteTestsTo.java b/src/main/java/com/diffblue/cover/annotations/WriteTestsTo.java new file mode 100644 index 0000000..0bd06f0 --- /dev/null +++ b/src/main/java/com/diffblue/cover/annotations/WriteTestsTo.java @@ -0,0 +1,57 @@ +/* + * Copyright 2025 Diffblue Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.diffblue.cover.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Directs Diffblue Cover to write tests for the annotated source class into the specified test + * class file. This annotation allows users to explicitly specify the target test class when it + * should not match the configured or default --class-name-template. + * + *

This annotation can only be applied at the class level. + * + *

The specified test class name must be alphanumeric. The resulting test class will be created + * in the test folder, under the same package structure as the source class. + * + *

Example usage: + * + *

{@code
+ * package com.example.myapp;
+ *
+ * @WriteTestsTo("CustomTestClassName")
+ * public class SourceClass {
+ *     // class implementation
+ * }
+ * // Tests will be written to: src/test/java/com/example/myapp/CustomTestClassName.java
+ * }
+ * + * @since Diffblue Cover 1.9.0 + */ +@Retention(RUNTIME) +@Target(TYPE) +public @interface WriteTestsTo { + + /** + * @return the alphanumeric name of the test class file where tests for the annotated source class + * should be written. The resulting test class will be placed in the test folder under the + * same package structure as the source class. + */ + String value(); +} diff --git a/src/test/java/com/example/WriteTestsToExample.java b/src/test/java/com/example/WriteTestsToExample.java new file mode 100644 index 0000000..8cc601b --- /dev/null +++ b/src/test/java/com/example/WriteTestsToExample.java @@ -0,0 +1,40 @@ +/* + * Copyright 2025 Diffblue Limited. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ +package com.example; + +import com.diffblue.cover.annotations.WriteTestsTo; + +/** + * Example demonstrating the use of the {@link WriteTestsTo} annotation. + * + *

This class uses the @WriteTestsTo annotation to direct Diffblue Cover to write tests into a + * custom test class named "CustomTestClass" rather than following the default naming template. + * + *

The resulting test class will be created at: src/test/java/com/example/CustomTestClass.java + */ +@WriteTestsTo("CustomTestClass") +public class WriteTestsToExample { + + public static String greet(String name) { + if (name == null || name.isEmpty()) { + return "Hello, World!"; + } + return "Hello, " + name + "!"; + } + + public static int add(int a, int b) { + return a + b; + } +} From d942c724f83b84c61d75b4c42a46110e6bfeaf50 Mon Sep 17 00:00:00 2001 From: Aiden fry Date: Fri, 5 Dec 2025 10:05:46 +0000 Subject: [PATCH 2/2] Up version the annotation library --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2df075f..3c9e309 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 4.0.0 com.diffblue.cover cover-annotations - 1.8.0 + 1.9.0 jar Cover Annotations