camunda-community-hub / camunda-process-test-coverage

Community Extension Helper library to visualize which parts of a BPMN process have been covered by a process test.
https://camunda-community-hub.github.io/camunda-process-test-coverage/
Apache License 2.0
75 stars 46 forks source link

Rebuild alternative using Spock Framework #32

Closed StephenOTT closed 2 years ago

StephenOTT commented 6 years ago

Hey

so building from the covo in #28 and #31, i have put together a alternative to the current coverage implementation:

sock-bpmn-coverage-digitalstate

This is a spock framework report that integrates BPMN-js and the coverage results.

I am iteratively adding features to be feature equivalent to the current workarounds that bpmnCoverage implements:

so far i have setups for:

  1. Activity Events, including Sequence flows
  2. Aync configs for all elements
  3. and user tasks listing.
Executed Activity Events:
[SequenceFlow_19totff, SequenceFlow_19ak290, SequenceFlow_1uq2whs, SequenceFlow_1rvhomr, SequenceFlow_1v7gx3u, SequenceFlow_152lh88, SequenceFlow_0qsbc3h, StartEvent_07ulwup, Task_1vbilbk, SubProcess_0nx9ieb, StartEvent_0bpfm61, Task_0dm0rll, EndEvent_03b850y, ExclusiveGateway_0jlwph2, Task_0qacez5, EndEvent_02dwqrb]

Async Element Configs:
[[id:Task_0dm0rll, asyncBefore:false, asyncAfter:false, exclusive:true], [id:Task_1vbilbk, asyncBefore:false, asyncAfter:false, exclusive:true], [id:SubProcess_0nx9ieb, asyncBefore:false, asyncAfter:false, exclusive:true], [id:ExclusiveGateway_0jlwph2, asyncBefore:false, asyncAfter:false, exclusive:true], [id:EndEvent_02dwqrb, asyncBefore:false, asyncAfter:false, exclusive:true], [id:EndEvent_03b850y, asyncBefore:false, asyncAfter:false, exclusive:true], [id:Task_0qacez5, asyncBefore:false, asyncAfter:false, exclusive:true], [id:StartEvent_07ulwup, asyncBefore:false, asyncAfter:false, exclusive:true], [id:StartEvent_0bpfm61, asyncBefore:false, asyncAfter:false, exclusive:true]]

User Tasks:
[Task_0qacez5]

The sequence flow is added currently using a Script that is added into the BPMN before the BPMN is loaded into the engine:

  def addExecutionListener(model, elementId, scriptResource, scriptFormat){
    // @TODO NOTE: The estLis had to be new for every instance
    CamundaExecutionListener extLis = model.newInstance(CamundaExecutionListener.class);
    CamundaScript camScript = model.newInstance(CamundaScript.class);
    camScript.setCamundaResource(scriptResource)
    camScript.setCamundaScriptFormat(scriptFormat)
    extLis.setCamundaEvent('take')
    extLis.setCamundaScript(camScript)

    def newModel = model.getModelElementById(elementId).builder().addExtensionElement(extLis).done()
    return newModel
  }

  def setupSequenceFlowListeners(model, scriptResource, scriptFormat){

    def sequenceFlows = model.getModelElementsByType(org.camunda.bpm.model.bpmn.instance.SequenceFlow.class).collect {it.getId()}

    def newModel = model
    sequenceFlows.each {
      newModel = addExecutionListener(newModel, it, scriptResource, scriptFormat)
    }
    return newModel
  }

and the script being:

var Date = Java.type("java.util.Date")

// https://docs.camunda.org/javadoc/camunda-bpm-platform/7.9/org/camunda/bpm/engine/impl/history/event/HistoricActivityInstanceEventEntity.html
var HistoricActivityInstanceEventEntity = Java.type('org.camunda.bpm.engine.impl.history.event.HistoricActivityInstanceEventEntity')

var historicActivityInstance = new HistoricActivityInstanceEventEntity()

historicActivityInstance.setActivityId(execution.getCurrentTransitionId())
historicActivityInstance.setExecutionId(execution.getId())
historicActivityInstance.setActivityType('sequenceFlow')
historicActivityInstance.setStartTime(new Date())
historicActivityInstance.setEndTime(new Date())
historicActivityInstance.setDurationInMillis(0)
historicActivityInstance.setProcessDefinitionId(execution.getProcessDefinitionId())
historicActivityInstance.setProcessInstanceId(execution.getProcessInstanceId())

// https://docs.camunda.org/javadoc/camunda-bpm-platform/7.9/org/camunda/bpm/engine/impl/history/handler/DbHistoryEventHandler.html
var historyHandler = Java.type('org.camunda.bpm.engine.impl.history.handler.DbHistoryEventHandler');
(new historyHandler).handleEvent(historicActivityInstance)

this script is going to be rebuilt as a Groovy script to align with Spock.

will also be making updates as per @ThorbenLindhauer discussions on: https://forum.camunda.org/t/execution-listener-on-sequence-flow-generate-history-event/7586/2

The goal of the coverage reporting is to provide a more script based reporting solution for Coverage; and make is very easy for typical technical business users to update reports with additional queries as they see fit.

We are also looking to only use built in camunda services and not modify the engine (so far so good / not needed, but it's not a hard "no"; just preferable) so we can easily add the unit testing to any implementation.

StephenOTT commented 6 years ago

Few things to still implement on the BPMN-js Javascript side: Transaction boundries, Aysnc, User Tasks, and executed activity colors. All of the data is available already, just need to add the logic in the final report.

Also the bare bones code is not setup to handle multiple BPMN definitions in a single spec, or when there are multiple pools in a single bpmn file. These were skipped as they were known issues that has solutions, it was just a matter of writing the final logic to do the "If/Then" conditions.

StephenOTT commented 6 years ago

Update:

spock-bpmn-coverage-digitalstate - v2

StephenOTT commented 6 years ago

Added Multi-Feature Support:

spock-bpmn-coverage-digitalstate - v3 - multiple features

StephenOTT commented 6 years ago

Here is a update with Multi-Iterations support and data tables:

spock-bpmn-coverage-digitalstate - v7

StephenOTT commented 6 years ago

Added support for receive tasks, catch events, and unfinished activities:

spock-bpmn-coverage-digitalstate - v8

StephenOTT commented 6 years ago

Updated with further refinements and better theme

bpmn unit test v5

ThorbenLindhauer commented 6 years ago

The font would freak me out ;)

StephenOTT commented 6 years ago

Hah @ThorbenLindhauer I was thinking it gives the feel of "wireframes": like a process under review, in development.

It is just a bootswatch theme. So you can just flip out one theme for whichever.

But it also gives me comic sans feels.

StephenOTT commented 6 years ago

@ThorbenLindhauer is this more mentally stabilizing for you? ;)

Updated image: bpmn unit test v8

my goal was to use out of box bootstrap with basic/limited modifications as to keep the template as simple as possible for others to customize as they wish; rather than have a template riddled with complex theme adjustments.

ThorbenLindhauer commented 6 years ago

Love it

StephenOTT commented 6 years ago

Updated for support of activating a coverage map any point during the unit test. Meaning you can have multiple coverage snapshots during a single feature.

single feature usage: coverage-single-feature

Multi-feature usage: covage-multiple-features

xref: #35

StephenOTT commented 6 years ago

Here is a update from some discussions with the vPAV devs:

This is showing data flow/variable usage throughout process: when variables are created, updated, or deleted, the details are collated as part of the coverage to see where data modification is occurring.

coverage-with-writes

and a more overall view: coverage-variable-writes

StephenOTT commented 6 years ago

Hey!

Okay so i have wrapped the coverage builder into a isolated coverage builder that does not use Spock Framework or Spock Reports

https://github.com/DigitalState/camunda-coverage-generation-groovy

The tool allows someone to generate "coverageSnapshots" through their test(s) and then "save" the snapshots at any point.

The idea being that this can be incorporated into a larger testing report such as the Spock Reports examples in my previous posts above, or it can just be used stand alone to generate the HTML files.

StephenOTT commented 6 years ago

As per discussion on https://github.com/DigitalState/camunda-coverage-generation-groovy/issues/9, I have added a new class to the coverageGenerator which now allows JUnit and pure Java usage. Groovy is still a hard dep for under the hood operations, but you can now use the Coverage Generator directly from your Java Unit Tests

See: https://github.com/DigitalState/camunda-coverage-generation-groovy#using-the-coverage-builder-with-junit--pure-java

package coveragetest;

import io.digitalstate.camunda.coverage.bpmn.CoverageBuilderJavaBridge;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.test.Deployment;
import org.camunda.bpm.engine.test.ProcessEngineRule;

import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.*;

import org.junit.Rule;
import org.junit.Test;

/**
 * @author Daniel Meyer
 * @author Martin Schimak
 */
public class SimpleTestCase {

    @Rule
    public ProcessEngineRule rule = new ProcessEngineRule("camunda_config/camunda.cfg.xml");
    CoverageBuilderJavaBridge coverageBuilder = new CoverageBuilderJavaBridge();

    @Test
    @Deployment(resources = {"testProcess.bpmn"})
    public void shouldExecuteProcess() {
        // Given we create a new process instance
        ProcessInstance processInstance = runtimeService().startProcessInstanceByKey("testProcess");
        // Then it should be active
        assertThat(processInstance).isActive();
        // And it should be the only instance
        assertThat(processInstanceQuery().count()).isEqualTo(1);
        // And there should exist just a single task within that process instance
        assertThat(task(processInstance)).isNotNull();

        // When we complete that task
        complete(task(processInstance));
        // Then the process instance should be ended
        assertThat(processInstance).isEnded();

        coverageBuilder.coverageSnapshot(processInstance);
        coverageBuilder.saveCoverageSnapshots();

    }

}

and remember you can do interesting coverage generation scenarios such as:

package coveragetest;

import io.digitalstate.camunda.coverage.bpmn.CoverageBuilderJavaBridge;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.test.Deployment;
import org.camunda.bpm.engine.test.ProcessEngineRule;

import static org.camunda.bpm.engine.test.assertions.ProcessEngineTests.*;

import org.junit.Rule;
import org.junit.Test;

/**
 * @author Daniel Meyer
 * @author Martin Schimak
 */
public class SimpleTestCase {

    @Rule
    public ProcessEngineRule rule = new ProcessEngineRule("camunda_config/camunda.cfg.xml");
    CoverageBuilderJavaBridge coverageBuilder = new CoverageBuilderJavaBridge();

    @Test
    @Deployment(resources = {"testProcess.bpmn"})
    public void shouldExecuteProcess() {
        // Given we create a new process instance
        ProcessInstance processInstance = runtimeService().startProcessInstanceByKey("testProcess");
        // Then it should be active
        assertThat(processInstance).isActive();
        // And it should be the only instance
        assertThat(processInstanceQuery().count()).isEqualTo(1);
        // And there should exist just a single task within that process instance
        assertThat(task(processInstance)).isNotNull();

        coverageBuilder.coverageSnapshot(processInstance);

        // When we complete that task
        complete(task(processInstance));
        // Then the process instance should be ended
        assertThat(processInstance).isEnded();

        coverageBuilder.coverageSnapshot(processInstance);
        coverageBuilder.saveCoverageSnapshots();

    }

}

which would generate two snapshots: first would be before the User Task is completed, and the second would be after the process instance has completed.

StephenOTT commented 6 years ago

I have added further refinements to support more features:

As of 0.7

  1. when coverages are saved, they are saved into a folder based on the fully qualified class name of the class making the coverage save.
  2. Originally all bpmn.js was pulled from the CDN, but given some env do not have internet access, a local version is also supplied. When exporting coverages you can set the argument to false to ensure that the .html files generated use the local bpmn.js file.