diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index 705f2736..1b310e3e 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -1,12 +1,34 @@ ## Implementation Notes -- Via the parse listener plugin we register execution listeners on sequence flows so coverage on these can be recorded. -- Via the history handler we trace execution of flow elements so coverage on these can be recorded. -- When the tests are run, for each passing token information about the process instance and the covered element is recorded as [CoveredElement](src/main/java/org/camunda/bpm/extension/process_test_coverage/trace/CoveredElement.java) in the trace of covered elements. Also the visual reports are updated with the covered element. -- Also the information which deployments happened is recorded so the expected number of traced elements can be calculated from the bpmns -- In the tests you specify which kind and percentage of coverage you want to check. The trace of covered elements gets filtered accordingly and is used to assert certain properties (e.g. percentage of flow nodes covered, percentage of sequence flows covered, or that certain elements have been covered). -- Builders are used to abstract away the construction of Coverages and JUnit Rules and provide a stable interface and a nice programming experience -- The bpmns used in unit tests use both the old and new camunda namespaces for easy testing with different camunda versions (new namespaces starting with camunda 7.2.6, 7.3.3, 7.4.0) -- As example use case, in wdw-elab, we set the global minimal coverage property defined in [TestCoverageProcessEngineRuleBuilder.DEFAULT_ASSERT_AT_LEAST_PROPERTY](src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/TestCoverageProcessEngineRuleBuilder.java) as build parameter in our jenkins builds to specify the necessary coverage per project. + +Refer to the UML to get a fast perspective on the coverage rule internals. + +### The Story + + +- The post BPMN parse listener and execution listener are registered with the process engine configuration. +- Via the post bpmn parse listener we register execution listeners on sequence flows so coverage on these can be recorded. +- Via the history handler/listener we trace execution of flow elements so coverage on these can be recorded. +- Before every test method execution the "starting" method of the rule is executed. + - The process engine state is initialized. + - The test run state is initialized if it is the first run for the rule. (The rule is initialized once if a @ClassRule, otherwise a rule including the state is initialized for each test method.) The state is also assigned to the listeners to be able to keep track of covered elements. + - The process engine is started and process definitions deployed. + - The deployment is registered with the run state creating a new method coverage. +- The test is executed. + - During the execution the listeners are adding the covered elements to the coverage state. In turn the coverage state is aggregating the class coverage. + - The class coverage is a tree. It is aggregated from individual test method coverages. The test method coverages are aggregations of individual process coverages of the deployed process definitions. The process coverages are aggregations of covered elements. By adding a covered element to the class coverage it takes its rightful place in the structure according to the data stored in it and the method by which it has been deployed. +- After the test execution the rules "finished" method is executed. + - The test method coverage is retrieved from the state, logged and asserted with against assertions added for the given method (rule.addTestMethodCoverageAssertionMatcher(testName, matcher)). A graphical coverage report is generated for all deployed process definitions. + - If the rule is a @ClassRule the last run of "finished" logs and asserts the class coverage which is again retrieved from the state. A graphical coverage report is generated for all deployed process definitions. + - The super class process engine rule handles deployment cleanup. In case of a @ClassRule run process engine cleanup is done. + +### Notes + +- The bpmns used in the rule unit tests use both the old and new camunda namespaces for easy testing with different camunda versions (new namespaces starting with camunda 7.2.6, 7.3.3, 7.4.0) +- As example use case, in wdw-elab, we set the global minimal coverage property defined in [TestCoverageProcessEngineRuleBuilder.DEFAULT_ASSERT_AT_LEAST_PROPERTY](src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/TestCoverageProcessEngineRuleBuilder.java) as a build parameter in our jenkins builds to specify the necessary coverage per project. + +## UML + +![UML](class-diagram.png) ## Resources * Use the source, Luke. diff --git a/class-diagram.png b/class-diagram.png new file mode 100644 index 00000000..6838d5ad Binary files /dev/null and b/class-diagram.png differ diff --git a/class-diagram.ucls b/class-diagram.ucls new file mode 100644 index 00000000..d1a56408 --- /dev/null +++ b/class-diagram.ucls @@ -0,0 +1,303 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/CoverageTestRunState.java b/src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/CoverageTestRunState.java index 79d23794..aa548315 100644 --- a/src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/CoverageTestRunState.java +++ b/src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/CoverageTestRunState.java @@ -15,7 +15,7 @@ * State tracking the current class and method coverage run. * * @author grossax - * @author okicir + * @author z0rbas */ public class CoverageTestRunState { @@ -63,7 +63,7 @@ public void addCoveredElement(/* @NotNull */ CoveredElement coveredElement) { * @param testName * The name of the test method. */ - public void addTestMethodRun(ProcessEngine processEngine, String deploymentId, + public void initializeTestMethodCoverage(ProcessEngine processEngine, String deploymentId, List processDefinitions, String testName) { final MethodCoverage testCoverage = new MethodCoverage(deploymentId); diff --git a/src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/TestCoverageProcessEngineRule.java b/src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/TestCoverageProcessEngineRule.java index 3edd02d5..0b2fd5bf 100644 --- a/src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/TestCoverageProcessEngineRule.java +++ b/src/main/java/org/camunda/bpm/extension/process_test_coverage/junit/rules/TestCoverageProcessEngineRule.java @@ -38,12 +38,12 @@ * This rule cannot be used as a @ClassRule without the @Rule annotation. * * @author grossax - * @author okicir + * @author z0rbas * */ public class TestCoverageProcessEngineRule extends ProcessEngineRule { - public static Logger logger = Logger.getLogger(TestCoverageProcessEngineRule.class.getCanonicalName()); + private static Logger logger = Logger.getLogger(TestCoverageProcessEngineRule.class.getCanonicalName()); /** * The state of the current run (class and current method). @@ -108,12 +108,9 @@ public void starting(Description description) { initializeRunState(description); - // Enable our coverage listeners - configureProcessEngine(); - super.starting(description); - registerDeployments(description); + initializeMethodCoverage(description); } @Override @@ -170,19 +167,19 @@ private void validateRuleAnnotations(Description description) { } /** - * Register the process deployments of the current test method with our run - * state. + * Initialize the current test method coverage. * * @param description */ - private void registerDeployments(Description description) { + private void initializeMethodCoverage(Description description) { + // Not a @ClassRule run and deployments present if (deploymentId != null) { - final List deployedProcessDefinitions = processEngine.getRepositoryService().createProcessDefinitionQuery().deploymentId( - deploymentId).list(); + final List deployedProcessDefinitions = processEngine.getRepositoryService() + .createProcessDefinitionQuery().deploymentId(deploymentId).list(); - coverageTestRunState.addTestMethodRun(processEngine, + coverageTestRunState.initializeTestMethodCoverage(processEngine, deploymentId, deployedProcessDefinitions, description.getMethodName()); @@ -205,6 +202,8 @@ private void initializeRunState(final Description description) { coverageTestRunState = new CoverageTestRunState(); coverageTestRunState.setTestClassName(description.getClassName()); + initializeListenerRunState(); + firstRun = false; } @@ -213,10 +212,10 @@ private void initializeRunState(final Description description) { } /** - * Configures the process engine listeners responsible for the coverage + * Sets the test run state for the coverage listeners. * logging. {@see ProcessCoverageInMemProcessEngineConfiguration} */ - private void configureProcessEngine() { + private void initializeListenerRunState() { final ProcessEngineConfigurationImpl processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration(); diff --git a/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/ClassCoverage.java b/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/ClassCoverage.java index e393bc58..17def2ef 100644 --- a/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/ClassCoverage.java +++ b/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/ClassCoverage.java @@ -15,7 +15,7 @@ /** * Test class coverage model. The class coverage is an aggregation of all test method coverages. * - * @author okicir + * @author z0rbas * */ public class ClassCoverage implements Coverage { @@ -23,7 +23,7 @@ public class ClassCoverage implements Coverage { /** * Map connecting the test method to the test method run coverage. */ - private Map testToDeploymentCoverage = new HashMap(); + private Map testNameToMethodCoverage = new HashMap(); /** * Adds a covered element to the test method coverage. @@ -32,7 +32,7 @@ public class ClassCoverage implements Coverage { * @param coveredElement */ public void addCoveredElement(String testName, CoveredElement coveredElement) { - testToDeploymentCoverage.get(testName).addCoveredElement(coveredElement); + testNameToMethodCoverage.get(testName).addCoveredElement(coveredElement); } /** @@ -42,7 +42,7 @@ public void addCoveredElement(String testName, CoveredElement coveredElement) { * @return */ public MethodCoverage getTestMethodCoverage(String testName) { - return testToDeploymentCoverage.get(testName); + return testNameToMethodCoverage.get(testName); } /** @@ -52,7 +52,7 @@ public MethodCoverage getTestMethodCoverage(String testName) { * @param testCoverage */ public void addTestMethodCoverage(String testName, MethodCoverage testCoverage) { - testToDeploymentCoverage.put(testName, testCoverage); + testNameToMethodCoverage.put(testName, testCoverage); } /** @@ -89,7 +89,7 @@ public Set getCoveredFlowNodes() { final Set coveredFlowNodes = new TreeSet(CoveredElementComparator.instance()); - for (MethodCoverage deploymentCoverage : testToDeploymentCoverage.values()) { + for (MethodCoverage deploymentCoverage : testNameToMethodCoverage.values()) { coveredFlowNodes.addAll(deploymentCoverage.getCoveredFlowNodes()); } @@ -104,7 +104,7 @@ public Set getCoveredFlowNodes() { public Set getCoveredFlowNodeIds(String processDefinitionKey) { final Set coveredFlowNodeIds = new HashSet(); - for (MethodCoverage methodCoverage : testToDeploymentCoverage.values()) { + for (MethodCoverage methodCoverage : testNameToMethodCoverage.values()) { coveredFlowNodeIds.addAll(methodCoverage.getCoveredFlowNodeIds(processDefinitionKey)); } @@ -122,7 +122,7 @@ public Set getCoveredSequenceFlows() { final Set coveredSequenceFlows = new TreeSet(CoveredElementComparator.instance()); - for (MethodCoverage deploymentCoverage : testToDeploymentCoverage.values()) { + for (MethodCoverage deploymentCoverage : testNameToMethodCoverage.values()) { coveredSequenceFlows.addAll(deploymentCoverage.getCoveredSequenceFlows()); @@ -138,7 +138,7 @@ public Set getCoveredSequenceFlows() { public Set getCoveredSequenceFlowIds(String processDefinitionKey) { final Set coveredSequenceFlowIds = new HashSet(); - for (MethodCoverage methodCoverage : testToDeploymentCoverage.values()) { + for (MethodCoverage methodCoverage : testNameToMethodCoverage.values()) { coveredSequenceFlowIds.addAll(methodCoverage.getCoveredSequenceFlowIds(processDefinitionKey)); } @@ -163,7 +163,7 @@ public Set getProcessDefinitions() { private MethodCoverage getAnyMethodCoverage() { // All deployments must be the same, so we take the first one - final MethodCoverage anyDeployment = testToDeploymentCoverage.values().iterator().next(); + final MethodCoverage anyDeployment = testNameToMethodCoverage.values().iterator().next(); return anyDeployment; } @@ -173,7 +173,7 @@ private MethodCoverage getAnyMethodCoverage() { public void assertAllDeploymentsEqual() { Set processDefinitions = null; - for (MethodCoverage methodCoverage : testToDeploymentCoverage.values()) { + for (MethodCoverage methodCoverage : testNameToMethodCoverage.values()) { Set deploymentProcessDefinitions = methodCoverage.getProcessDefinitions(); diff --git a/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/CoveredActivity.java b/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/CoveredActivity.java index a92bbf5e..a4d475f4 100644 --- a/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/CoveredActivity.java +++ b/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/CoveredActivity.java @@ -4,7 +4,7 @@ * An activity covered by a test. * * @author grossax - * @author okicir + * @author z0rbas * */ public class CoveredActivity extends CoveredElement { diff --git a/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/CoveredElement.java b/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/CoveredElement.java index 4b71d020..1a00cf7f 100644 --- a/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/CoveredElement.java +++ b/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/CoveredElement.java @@ -3,7 +3,7 @@ /** * An element covered by a test. * - * @author okicir + * @author z0rbas * */ public abstract class CoveredElement { diff --git a/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/MethodCoverage.java b/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/MethodCoverage.java index 7721a457..85b52793 100644 --- a/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/MethodCoverage.java +++ b/src/main/java/org/camunda/bpm/extension/process_test_coverage/model/MethodCoverage.java @@ -19,7 +19,7 @@ * A test method annotated with @Deployment does an independent deployment of the listed * resources, hence this coverage is equivalent to a deployment coverage. * - * @author okicir + * @author z0rbas * */ public class MethodCoverage implements Coverage { @@ -124,14 +124,6 @@ private double getCoveragePercentage(Set coveredFlowNodes, Set getDefinitionFlowNodes() { return definitionFlowNodes; } - public void setDefinitionFlowNodes(Set definitionFlowNodes) { - this.definitionFlowNodes = definitionFlowNodes; - } - public Set getDefinitionSequenceFlows() { return definitionSequenceFlows; } - public void setDefinitionSequenceFlows(Set definitionSequenceFlows) { - this.definitionSequenceFlows = definitionSequenceFlows; - } - public Set getCoveredFlowNodes() { return coveredFlowNodes; } @@ -158,10 +131,6 @@ public Set getCoveredFlowNodeIds() { return coveredFlowNodeIds; } - public void setCoveredFlowNodes(Set coveredFlowNodes) { - this.coveredFlowNodes = coveredFlowNodes; - } - public Set getCoveredSequenceFlows() { return coveredSequenceFlows; } @@ -177,18 +146,10 @@ public Set getCoveredSequenceFlowIds() { return sequenceFlowIds; } - public void setCoveredSequenceFlows(Set coveredSequenceFlows) { - this.coveredSequenceFlows = coveredSequenceFlows; - } - public ProcessDefinition getProcessDefinition() { return processDefinition; } - public void setProcessDefinition(ProcessDefinition processDefinition) { - this.processDefinition = processDefinition; - } - public String getProcessDefinitionId() { return processDefinition.getId(); } @@ -210,4 +171,24 @@ public String toString() { coveredSequenceFlows.size(), definitionSequenceFlows.size()); // Sequence flows } + + /** + * Retrieves the number of covered flow node and sequence flow elements. + * + * @return + */ + private int getNumberOfAllCovered() { + return coveredFlowNodes.size() + coveredSequenceFlows.size(); + } + + /** + * Retrieves the number of flow node and sequence flow elements for the + * process definition. + * + * @return + */ + private int getNumberOfAllDefined() { + return definitionFlowNodes.size() + definitionSequenceFlows.size(); + } + } diff --git a/src/main/java/org/camunda/bpm/extension/process_test_coverage/util/BpmnJsReport.java b/src/main/java/org/camunda/bpm/extension/process_test_coverage/util/BpmnJsReport.java index 3c8b8e7f..c94c081b 100644 --- a/src/main/java/org/camunda/bpm/extension/process_test_coverage/util/BpmnJsReport.java +++ b/src/main/java/org/camunda/bpm/extension/process_test_coverage/util/BpmnJsReport.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.text.MessageFormat; import java.text.NumberFormat; import java.util.Collection; import java.util.zip.ZipEntry; @@ -20,17 +21,45 @@ */ public class BpmnJsReport { + /** + * Template used for all coverage reports. + */ private static final String REPORT_TEMPLATE = "bpmn.js-report-template.html"; + /** + * Placeholder to be replaced with the process key. + */ protected static final String PLACEHOLDER_PROCESS_KEY = "//PROCESSKEY"; + + /** + * Placeholder to be replaced with the process coverage percentage. + */ protected static final String PLACEHOLDER_COVERAGE = "//COVERAGE"; + + /** + * Placeholder to be replaced with the test class full qualified name. + */ protected static final String PLACEHOLDER_TESTCLASS = "//TESTCLASS"; + + /** + * Placeholder to be replaced with the test method name. + */ protected static final String PLACEHOLDER_TESTMETHOD = "//TESTMETHOD"; + + /** + * Placeholder to be replaced with the addMarker annotation for all flow nodes. (canvas.addMarker()) + */ protected static final String PLACEHOLDER_ANNOTATIONS = " //YOUR ANNOTATIONS GO HERE"; + + /** + * Placeholder to be replaces with the BPMN content. + */ protected static final String PLACEHOLDER_BPMN_XML = "YOUR BPMN XML CONTENT"; - protected static final String SEQUENCEFLOW_ANNOTATION_PREFIX = "\\$( \"g[data-element-id='"; - protected static final String SEQUENCEFLOW_ANNOTATION_POSTFIX = "']\" ).find('path').attr('stroke', '#00ff00');\n"; + /** + * JQuery command used for sequence flow SVG arrows coverage coloring. + */ + protected static final String JQUERY_SEQUENCEFLOW_MARKING_COMMAND = "\\$( \"g[data-element-id='{0}']\" ).find('path').attr('stroke', '#00ff00');\n"; public static void highlightFlowNodesAndSequenceFlows(String bpmnXml, Collection activityIds, Collection sequenceFlowIds, String reportName, String processDefinitionKey, @@ -91,13 +120,14 @@ protected static String generateJavaScriptFlowNodeAnnotations(Collection } protected static String generateJavaScriptSequenceFlowAnnotations(Collection sequenceFlowIds) { + StringBuilder javaScript = new StringBuilder(); for (String sequenceFlowId : sequenceFlowIds) { + javaScript.append("\t\t\t"); - javaScript.append(SEQUENCEFLOW_ANNOTATION_PREFIX); - javaScript.append(sequenceFlowId); - javaScript.append(SEQUENCEFLOW_ANNOTATION_POSTFIX); + javaScript.append(MessageFormat.format(JQUERY_SEQUENCEFLOW_MARKING_COMMAND, sequenceFlowId)); } + return javaScript.toString(); }