From a13053ca611d8f8fa46dc5d9a0b4ee0cbdb84533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Pfl=C3=BCgner?= Date: Mon, 9 Dec 2024 15:38:36 +0100 Subject: [PATCH 1/3] Extended the constraint java:TestMethodWithoutAssertions to support expected exceptions provided by the JUnit @Test annotation --- .../META-INF/jqassistant-rules/java.xml | 80 +++++++++++++++++-- .../plugin/java/test/rules/JavaTestIT.java | 44 +++++++++- .../jqassistant-rules/junit-common.xml | 4 +- .../META-INF/jqassistant-rules/junit4.xml | 17 ++++ .../plugin/junit/test/rule/Junit4IT.java | 26 ++++++ .../plugin/junit/test/rule/Junit5IT.java | 5 +- .../test/set/junit4/Assertions4Junit4.java | 3 + 7 files changed, 166 insertions(+), 13 deletions(-) diff --git a/plugin/java/src/main/resources/META-INF/jqassistant-rules/java.xml b/plugin/java/src/main/resources/META-INF/jqassistant-rules/java.xml index 135a7f4d1a..d2b211eee7 100644 --- a/plugin/java/src/main/resources/META-INF/jqassistant-rules/java.xml +++ b/plugin/java/src/main/resources/META-INF/jqassistant-rules/java.xml @@ -605,24 +605,88 @@ ]]> - + + + An assert annotation is used to define an expected testing result (e.g. @Test(expected = RuntimeException.class) + for a test method. + + (method:Method)-[:ANNOTATED_BY]->(annotation:Annotation:Assert)-[:OF_TYPE]->(annotationType:Java:Type) + RETURN + type AS DeclaringType, method AS AnnotatedTestMethod, annotationType AS AnnotationType + ORDER BY + type.fqn, method.fqn, annotationType.fqn + ]]> + + + + Returns all test methods performing at least one assertion. + (testMethod:Method)-[:PERFORMS_ASSERTION]->(assertion:Assert) + RETURN + type AS DeclaringType, testMethod AS TestMethod + ORDER BY + type.fqn, testMethod.signature + ]]> + + + + - All test methods must perform assertions (within a call hierarchy of max. 3 steps). + + A test method performs a method assertion, if it invokes an assert method within a call hierarchy of max. 3 steps. + (testMethod:Test:Method) OPTIONAL MATCH - path=shortestPath((testMethod)-[:INVOKES|VIRTUAL_INVOKES*]->(assert:Method:Assert)) + (testMethod)-[invokes:INVOKES|VIRTUAL_INVOKES*]->(assertMethod:Method:Assert), path=shortestPath((testMethod)-[:INVOKES|VIRTUAL_INVOKES*]->(assertMethod)) WITH - testClass, testMethod, COLLECT(length(path)) as lengths - WITH - testClass, testMethod, REDUCE(minVal = lengths[0], n IN lengths | CASE WHEN n < minVal THEN n ELSE minVal END) AS shortestPathLength + testClass, testMethod, assertMethod, length(path) as callDepth WHERE - shortestPathLength is null - or shortestPathLength > $javaTestAssertMaxCallDepth + callDepth is not null + and callDepth <= $javaTestAssertMaxCallDepth + MERGE + (testMethod)-[:PERFORMS_ASSERTION]->(assertMethod) + RETURN + distinct testClass AS TestClass, testMethod AS TestMethod, assertMethod as AssertMethod + ORDER BY + testClass.fqn, testMethod.name, AssertMethod.name + ]]> + + + + + + + + A test method performs an annotation assertion, if it is annotated with an assert annotation. + + (testMethod:Test:Method)-[:ANNOTATED_BY]->(assertAnnotation:Annotation:Assert)-[:OF_TYPE]->(annotationType:Java:Type) + MERGE + (testMethod)-[:PERFORMS_ASSERTION]->(assertAnnotation) + RETURN + distinct testClass AS TestClass, testMethod as TestMethod, annotationType AS AssertAnnotationType + ORDER BY + testClass.fqn, testMethod.fqn + ]]> + + + + + All test methods must perform at least one assertion. + (testMethod:Test:Method) + WHERE NOT + (testMethod)-[:PERFORMS_ASSERTION]->(:Assert) RETURN distinct testClass AS TestClass, testMethod AS TestMethod ORDER BY diff --git a/plugin/java/src/test/java/com/buschmais/jqassistant/plugin/java/test/rules/JavaTestIT.java b/plugin/java/src/test/java/com/buschmais/jqassistant/plugin/java/test/rules/JavaTestIT.java index a6e6a5368b..19b7b04720 100644 --- a/plugin/java/src/test/java/com/buschmais/jqassistant/plugin/java/test/rules/JavaTestIT.java +++ b/plugin/java/src/test/java/com/buschmais/jqassistant/plugin/java/test/rules/JavaTestIT.java @@ -25,6 +25,9 @@ class JavaTestIT extends AbstractJavaPluginIT { void setUp() { query( "MERGE (:Artifact)-[:CONTAINS]->(:Java:ByteCode:Type:Class{fqn:'Test'})-[:DECLARES]->(:Java:ByteCode:Member:Method:Test{signature:'void test()'})-[:INVOKES]->(:Java:ByteCode:Member:Method)-[:INVOKES]->(:Java:ByteCode:Member:Method:Assert)<-[:DECLARES]-(:Java:ByteCode:Type{fqn:'Assertions'})"); + query( + "MERGE (testType:Java:ByteCode:Type:Class{fqn:'Test'})-[:DECLARES]->(testMethod:Java:ByteCode:Member:Method:Test{signature:'void annotatedTest()'})-[:ANNOTATED_BY]->(:Java:ByteCode:Annotation:Assert)-[:OF_TYPE]->(:Java:ByteCode:Type {fqn: 'AnnotationType'})" + ); } @Test @@ -69,8 +72,42 @@ void javaAssertMethod() throws RuleException { } @Test - void javaTestMethodWithoutAssertion() throws RuleException { + void javaAssertAnnotation() throws RuleException { + Result result = applyConcept("java:AssertAnnotation"); + assertThat(result.getStatus()).isEqualTo(SUCCESS); + assertThat(result.getRows()).hasSize(1); + store.beginTransaction(); + Map> columns = result.getRows() + .get(0) + .getColumns(); + assertThat(columns.get("DeclaringType").getLabel()).isEqualTo("Test"); + assertThat(columns.get("AnnotatedTestMethod").getLabel()).isEqualTo("void annotatedTest()"); + assertThat(columns.get("AnnotationType").getLabel()).isEqualTo("AnnotationType"); + store.commitTransaction(); + } + + @Test + void javaPerformsAssertion() throws RuleException { + Result result = applyConcept("java:PerformsAssertion"); + assertThat(result.getStatus()).isEqualTo(SUCCESS); + assertThat(result.getRows()).hasSize(2); + Map> annotationAssertion = result.getRows().get(0).getColumns(); + Map> methodAssertion = result.getRows().get(1).getColumns(); + store.beginTransaction(); + assertThat(annotationAssertion.get("DeclaringType").getLabel()).isEqualTo("Test"); + assertThat(annotationAssertion.get("TestMethod").getLabel()).isEqualTo("void annotatedTest()"); + assertThat(methodAssertion.get("DeclaringType").getLabel()).isEqualTo("Test"); + assertThat(methodAssertion.get("TestMethod").getLabel()).isEqualTo("void test()"); + store.commitTransaction(); + } + + @Test + void javaTestMethodAssertionWithinCallHierarchy() throws RuleException { assertThat(validateConstraint("java:TestMethodWithoutAssertion").getStatus()).isEqualTo(SUCCESS); + } + + @Test + void javaTestMethodAssertionOutOfCallHierarchy() throws RuleException { Result result = validateConstraint("java:TestMethodWithoutAssertion", Map.of("javaTestAssertMaxCallDepth", "1")); assertThat(result.getStatus()).isEqualTo(FAILURE); assertThat(result.getRows()).hasSize(1); @@ -82,4 +119,9 @@ void javaTestMethodWithoutAssertion() throws RuleException { assertThat(((Column) columns.get("TestMethod")).getValue()).isNotNull(); store.commitTransaction(); } + + @Test + void javaTestMethodAssertionViaAnnotation() throws RuleException { + assertThat(validateConstraint("java:TestMethodWithoutAssertion").getStatus()).isEqualTo(SUCCESS); + } } diff --git a/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit-common.xml b/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit-common.xml index 947c8fe570..658ee8ea04 100644 --- a/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit-common.xml +++ b/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit-common.xml @@ -89,14 +89,14 @@ - + All test methods must perform assertions (within a call hierarchy of max. 3 steps). This constraint has been replaced by "java:TestMethodWithoutAssertion". (testMethod:Test:Method) WHERE - NOT (testMethod)-[:INVOKES|VIRTUAL_INVOKES*..3]->(:Method:Assert) + NOT (testMethod)-[:PERFORMS_ASSERTION]->(:Assert) RETURN testType AS DeclaringType, testMethod AS Method diff --git a/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit4.xml b/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit4.xml index 00fbf63017..2fa47f134e 100644 --- a/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit4.xml +++ b/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit4.xml @@ -149,6 +149,23 @@ ]]> + + + + Labels @Test-Annotations with the parameter "expected" as assert annotations. + (annotatedTestMethod:Test:Method)-[:ANNOTATED_BY]->(annotation:Annotation)-[:OF_TYPE]->(:Java:Type {fqn: "org.junit.Test"}), + (annotation)-[:HAS]->(expectation:Java:Value {name: 'expected'}) + SET + annotation:Junit4:Assert + RETURN + testType AS DeclaringType, annotatedTestMethod AS AnnotatedTestMethod + ORDER BY + testType.fqn, annotatedTestMethod.name + ]]> + + Labels all classes annotated by "@org.junit.runners.Suite.SuiteClasses" with "Junit4" and "Suite" and creates a relation "CONTAINS_TESTCLASS" to all referenced classes. result = applyConcept("java:AssertAnnotation"); + store.beginTransaction(); + assertThat(result.getStatus(), equalTo(SUCCESS)); + Map> assertAnnotation = result.getRows() + .get(0) + .getColumns(); + assertThat(assertAnnotation.get("DeclaringType").getLabel(), endsWith("test.set.junit4.Assertions4Junit4")); + assertThat(assertAnnotation.get("AnnotatedTestMethod").getLabel(), equalTo("void testWithExpectedRuntimeException()")); + assertThat(assertAnnotation.get("AnnotationType").getLabel(), equalTo("org.junit.Test")); + store.commitTransaction(); + } + /** * Verifies the concept "junit4:BeforeMethod". * diff --git a/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/rule/Junit5IT.java b/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/rule/Junit5IT.java index 8ba8029c2c..6b30c73f6c 100644 --- a/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/rule/Junit5IT.java +++ b/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/rule/Junit5IT.java @@ -464,10 +464,11 @@ public void nonJUnit5TestMethod() throws Exception { .get("TestMethod") .getValue()) .collect(Collectors.toList()); - assertThat(rows.size(), equalTo(5)); + assertThat(rows.size(), equalTo(6)); assertThat(rows, containsInAnyOrder(is(methodDescriptor(Assertions4Junit4.class, "assertWithoutMessage")), is(methodDescriptor(Assertions4Junit4.class, "assertWithMessage")), is(methodDescriptor(Assertions4Junit4.class, "testWithoutAssertion")), - is(methodDescriptor(Assertions4Junit4.class, "testWithAssertion")), is(methodDescriptor(Assertions4Junit4.class, "testWithNestedAssertion")))); + is(methodDescriptor(Assertions4Junit4.class, "testWithAssertion")), is(methodDescriptor(Assertions4Junit4.class, "testWithNestedAssertion")), + is(methodDescriptor(Assertions4Junit4.class, "testWithExpectedRuntimeException")))); store.commitTransaction(); } diff --git a/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/set/junit4/Assertions4Junit4.java b/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/set/junit4/Assertions4Junit4.java index 4493f4c153..e0918d5cdb 100644 --- a/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/set/junit4/Assertions4Junit4.java +++ b/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/set/junit4/Assertions4Junit4.java @@ -33,6 +33,9 @@ public void testWithNestedAssertion() { nestedAssertion(); } + @Test(expected = RuntimeException.class) + public void testWithExpectedRuntimeException(){}; + private void nestedAssertion() { fail("Failing"); } From 25349e13bc62170b1f79342f09f0677d1b48aa27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Pfl=C3=BCgner?= Date: Mon, 9 Dec 2024 15:53:42 +0100 Subject: [PATCH 2/3] removed unused import --- .../buschmais/jqassistant/plugin/junit/test/rule/Junit4IT.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/rule/Junit4IT.java b/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/rule/Junit4IT.java index 66605a51a3..9164aaef1b 100644 --- a/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/rule/Junit4IT.java +++ b/plugin/junit/src/test/java/com/buschmais/jqassistant/plugin/junit/test/rule/Junit4IT.java @@ -13,7 +13,6 @@ import com.buschmais.jqassistant.core.rule.api.model.Constraint; import com.buschmais.jqassistant.core.rule.api.model.RuleException; import com.buschmais.jqassistant.core.shared.map.MapBuilder; -import com.buschmais.jqassistant.plugin.java.api.model.AnnotationDescriptor; import com.buschmais.jqassistant.plugin.java.api.model.MethodDescriptor; import com.buschmais.jqassistant.plugin.junit.test.set.junit4.Assertions4Junit4; import com.buschmais.jqassistant.plugin.junit.test.set.junit4.IgnoredTest; From 5e0d9aaff589340cdff5e1a5a9ddfcd84c58b39d Mon Sep 17 00:00:00 2001 From: Dirk Mahler Date: Thu, 12 Dec 2024 16:01:02 +0100 Subject: [PATCH 3/3] #721 renamed concept java:PerformsAssertion to java:MethodPerformsAssertion --- .../impl/report/JUnitReportPluginTest.java | 4 ++-- .../META-INF/jqassistant-rules/java.xml | 16 ++++++++-------- .../plugin/java/test/rules/JavaTestIT.java | 8 ++++---- .../META-INF/jqassistant-rules/junit-common.xml | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/plugin/common/src/test/java/com/buschmais/jqassistant/plugin/common/impl/report/JUnitReportPluginTest.java b/plugin/common/src/test/java/com/buschmais/jqassistant/plugin/common/impl/report/JUnitReportPluginTest.java index 47c42b5ee7..75e623bdc1 100644 --- a/plugin/common/src/test/java/com/buschmais/jqassistant/plugin/common/impl/report/JUnitReportPluginTest.java +++ b/plugin/common/src/test/java/com/buschmais/jqassistant/plugin/common/impl/report/JUnitReportPluginTest.java @@ -11,7 +11,7 @@ import com.buschmais.jqassistant.core.report.api.model.Column; import com.buschmais.jqassistant.core.report.api.model.Result; import com.buschmais.jqassistant.core.rule.api.model.*; -import com.buschmais.jqassistant.core.shared.xml.JAXBUnmarshaller; +import com.buschmais.jqassistant.core.shared.xml.JAXBHelper; import com.buschmais.jqassistant.plugin.junit.impl.schema.Error; import com.buschmais.jqassistant.plugin.junit.impl.schema.Failure; import com.buschmais.jqassistant.plugin.junit.impl.schema.Testcase; @@ -34,7 +34,7 @@ public class JUnitReportPluginTest extends AbstractReportPluginTest { private static final String EXPECTED_CONTENT = "c = foo\n" + "---\n" + "c = bar\n"; - private JAXBUnmarshaller unmarshaller = new JAXBUnmarshaller(Testsuite.class); + private JAXBHelper unmarshaller = new JAXBHelper(Testsuite.class); private Group testGroup = Group.builder().id("test:Group").description("testGroup").build(); private Concept concept = Concept.builder().id("test:Concept").description("testConcept").severity(Severity.MINOR).build(); diff --git a/plugin/java/src/main/resources/META-INF/jqassistant-rules/java.xml b/plugin/java/src/main/resources/META-INF/jqassistant-rules/java.xml index d2b211eee7..d71eaff1e1 100644 --- a/plugin/java/src/main/resources/META-INF/jqassistant-rules/java.xml +++ b/plugin/java/src/main/resources/META-INF/jqassistant-rules/java.xml @@ -620,20 +620,20 @@ ]]> - + Returns all test methods performing at least one assertion. (testMethod:Method)-[:PERFORMS_ASSERTION]->(assertion:Assert) + (type:Java:Type)-[:DECLARES]->(method:Method)-[:PERFORMS_ASSERTION]->(assertion:Assert) RETURN - type AS DeclaringType, testMethod AS TestMethod + type AS DeclaringType, method AS Method ORDER BY - type.fqn, testMethod.signature + type.fqn, method.signature ]]> - + @@ -645,7 +645,7 @@ MATCH (testClass:Java:Type)-[:DECLARES]->(testMethod:Test:Method) OPTIONAL MATCH - (testMethod)-[invokes:INVOKES|VIRTUAL_INVOKES*]->(assertMethod:Method:Assert), path=shortestPath((testMethod)-[:INVOKES|VIRTUAL_INVOKES*]->(assertMethod)) + path=shortestPath((testMethod)-[:INVOKES|VIRTUAL_INVOKES*]->(assertMethod:Method:Assert)) WITH testClass, testMethod, assertMethod, length(path) as callDepth WHERE @@ -661,7 +661,7 @@ - + @@ -680,7 +680,7 @@ - + All test methods must perform at least one assertion. result = applyConcept("java:PerformsAssertion"); + void javaMethodPerformsAssertion() throws RuleException { + Result result = applyConcept("java:MethodPerformsAssertion"); assertThat(result.getStatus()).isEqualTo(SUCCESS); assertThat(result.getRows()).hasSize(2); Map> annotationAssertion = result.getRows().get(0).getColumns(); Map> methodAssertion = result.getRows().get(1).getColumns(); store.beginTransaction(); assertThat(annotationAssertion.get("DeclaringType").getLabel()).isEqualTo("Test"); - assertThat(annotationAssertion.get("TestMethod").getLabel()).isEqualTo("void annotatedTest()"); + assertThat(annotationAssertion.get("Method").getLabel()).isEqualTo("void annotatedTest()"); assertThat(methodAssertion.get("DeclaringType").getLabel()).isEqualTo("Test"); - assertThat(methodAssertion.get("TestMethod").getLabel()).isEqualTo("void test()"); + assertThat(methodAssertion.get("Method").getLabel()).isEqualTo("void test()"); store.commitTransaction(); } diff --git a/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit-common.xml b/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit-common.xml index 658ee8ea04..e69500f640 100644 --- a/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit-common.xml +++ b/plugin/junit/src/main/resources/META-INF/jqassistant-rules/junit-common.xml @@ -89,7 +89,7 @@ - + All test methods must perform assertions (within a call hierarchy of max. 3 steps). This constraint has been replaced by "java:TestMethodWithoutAssertion".