[doc] Update "Testing your rules" for JUnit5

This commit is contained in:
Andreas Dangel
2023-01-17 13:57:59 +01:00
parent d05148e4bd
commit 125a016b36

View File

@ -3,7 +3,7 @@ title: Testing your rules
tags: [extending, userdocs]
summary: "Learn how to use PMD's simple test framework for unit testing rules."
permalink: pmd_userdocs_extending_testing.html
last_updated: July 2022
last_updated: January 2023
author: Andreas Dangel <andreas.dangel@adangel.org>
---
@ -15,7 +15,7 @@ Of course, the more tests, the better the rule is verified. If the rule is more
with which the behavior can be modified, then these different cases can also be tested.
And if there is a bug fix for a rule, be it a false positive or a false negative case, it should be accompanied
with an additional test case, so that the bug is not accidentally reintroduced later on.
by an additional test case, so that the bug is not accidentally reintroduced later on.
## How it works
@ -26,7 +26,7 @@ We have one test class per rule, which executes all test cases for a single rule
stored in separate XML files, for each rule a separate file is used.
All the test classes inherit from `net.sourceforge.pmd.testframework.PmdRuleTst`,
which provides the seamless integration with JUnit. This base class determines the language, the category name
which provides the seamless integration with JUnit5. This base class determines the language, the category name
and the rule name from the concrete test class. It then searches the test code on its own.
E.g. the individual rule test class
`net.sourceforge.pmd.lang.java.rule.bestpractices.AbstractClassWithoutAbstractMethodTest` tests the
@ -62,7 +62,7 @@ In general, the class name and file name pattern for the test class and data is
src/test/resources/net/sourceforge/pmd/lang/<Language Terse Name>/rule/<Category Name>/xml/<Rule Name>.xml
{%include tip.html content="This convention allows you to quickly find the test cases for a given rule:
Just search in the project for a file `<RuleName>.xml`. Search for a class `<Rule Name>Test` to find the
Just search in the project for a file `<Rule Name>.xml`. Search for a class `<Rule Name>Test` to find the
unit test class for the given rule. And if the rule is a Java-based rule, the search for `<Rule Name>Rule`
finds the rule implementation class." %}
@ -81,7 +81,7 @@ package net.sourceforge.pmd.lang.java.rule.bestpractices;
import net.sourceforge.pmd.testframework.PmdRuleTst;
public class AbstractClassWithoutAbstractMethodTest extends PmdRuleTst {
class AbstractClassWithoutAbstractMethodTest extends PmdRuleTst {
// no additional unit tests
}
```
@ -129,7 +129,7 @@ The root element is `<test-data>`. It can contain one or more `<test-code>` and
Each `<test-code>` element defines a single test case. `<code-fragment>` elements are used to share code snippets
between different test cases.
{%include note.html content="The XML schema is available at [rule-tests.xsd](https://github.com/pmd/pmd/blob/master/pmd-test/src/main/resources/rule-tests_1_0_0.xsd)." %}
{%include note.html content="The XML schema is available at [rule-tests.xsd](https://github.com/pmd/pmd/blob/master/pmd-test-schema/src/main/resources/net/sourceforge/pmd/test/schema/rule-tests_1_0_0.xsd)." %}
### `<test-code>` attributes
@ -139,17 +139,6 @@ The `<test-code>` elements understands the following optional attributes:
* **focused**: By default, it's `false`. Set it to `true`, to ignore all other test cases.
* **useAuxClasspath**: _deprecated since PMD 6.48.0: assumed true, has no effect anymore._
By default, it's `true`. Set it to `false` to reproduce issues which only
appear without type resolution.
* **reinitializeRule**: _deprecated since PMD 6.48.0: assumed true, has no effect anymore._
By default, it's `true`, so each test case starts with a fresh instantiated rule. Set it
to `false` to reproduce cases, where the previous run has influences.
* **regressionTest**: _deprecated since PMD 6.48.0: Use `disabled` instead. Note: It has the opposite boolean
semantic._ By default, it's `true`. Set it to `false`, to ignore and skip a test case.
### `<test-code>` children
* **`<description>`**: Short description of the test case. This will be the JUnit test name in the report.
@ -196,7 +185,7 @@ in a "CDATA" section, so that no further XML escapes (entity references such as
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests https://pmd.sourceforge.io/rule-tests_1_0_0.xsd">
<test-code reinitializeRule="true" disabled="false" useAuxClasspath="true">
<test-code disabled="false">
<description>Just a description, will be used as the test name for JUnit in the reports</description>
<rule-property name="somePropName">propValue</rule-property> <!-- optional -->
<expected-problems>2</expected-problems>
@ -237,7 +226,7 @@ better readability. The description can be written directly without a CDATA sect
## Using the test framework externally
It is also possible to use the test framework for custom rules developed outside the PMD source base.
Therefore you just need to reference the dependency `net.sourceforge.pmd:pmd-test`.
In order to use the test framework you just need to reference the dependency `net.sourceforge.pmd:pmd-test`.
For maven, you can use this snippet:
@ -260,9 +249,9 @@ package com.example.pmd.rules;
import net.sourceforge.pmd.testframework.SimpleAggregatorTst;
public class CustomRuleTest extends SimpleAggregatorTst {
class CustomRuleTest extends SimpleAggregatorTst {
@Override
public void setUp() {
protected void setUp() {
addRule("com/example/pmd/ruleset.xml", "CustomRule");
}
}
@ -276,27 +265,19 @@ The test data should be placed in an xml file located in "src/test/resources" un
## How the test framework is implemented
The framework uses a custom JUnit test runner under the hood, among a couple of utility classes:
The framework uses the [dynamic test feature](https://junit.org/junit5/docs/current/user-guide/#writing-tests-dynamic-tests)
of JUnit5 under the hood, among a couple of utility classes:
* `PmdRuleTst`: This is the base class for tests in PMD's code base. It is a subclass of `RuleTst` and just
contains the logic to determine the test resources based on the test class name.
contains the logic to determine the test resources based on the test class name.
* `SimpleAggregatorTst`: This is a more generic base class for the test classes and defines
the custom JUnit test runner. It doesn't register any test cases on its own.
It itself is a subclass of `RuleTst`.
* `SimpleAggregatorTst`: This is a more generic base class for the test classes.
It doesn't register any test cases on its own. You can register your own rule tests.
It itself is a subclass of `RuleTst`.
* The maven module "pmd-test-schema" contains the logic to parse the XML files and provide a `RuleTestCollection`. This
in turn contains a list of `RuleTestDescriptor`s. Each rule test descriptor describes a single test case.
* `RuleTst`: uses the `TestSchemaParser` from module "pmd-test-schema" to parse the test cases, executes each
rule test descriptor and asserts the results.
* `PMDTestRunner`: A custom JUnit test runner, that combines two separate test runners: The custom `RuleTestRunner`
and the standard `JUnit4` test runner. This combination allows you to add additional standard unit test methods
annotated with `@Test` to your test class.
*Note:* Since the test class is executed through two test runners, it is actually instantiated twice. Be aware
of this, if you do any initialization in the constructor. Also, the static hooks `@BeforeClass` and `@AfterClass`
will be executed twice.
* `RuleTestRunner`: This test runner executes the test descriptors with the help of `RuleTst`.
rule test descriptor and asserts the results. It defines a test method `ruleTests()` which is a test factory
and returns one dynamic test per rule test.