When an application class is deleted, the expected behavior is that the JUnit module should automatically remove the test class that corresponds to a deleted class.

In practice, when the end-user deletes a class from his model the module is supposed to:

  • Check whether the deleted class has a corresponding test case class or not.

  • Delete the test case class if it exists.

Technically, this means that our JUnit module has to register itself to receive "Delete Element" events.

Later, when such events occur, the JUnit module is supposed to process these events and clean up the test model accordingly.

A bit of theory: module model events

Implementation

The processing of the model changes is delegated to a JUnitModelChangeHandler object. This delegate is created and registered as a model change handler in the start() session service method. It is removed in the stop() method.

Handler is used rather than listener because, our code will modify the model to remove test case classes on model classes destruction.

The JUnitModelChangeHandler class must implement the IModelChangeHandler interface in order to be registered as a model change handler. This interface has only one method which will be called each time a top level transaction is successfully committed and which receives a ModelChangeEvent object as parameter.

In our case, we are only interested in deleted elements that are classes and that have a «JUnit» stereotype dependency to a test class. For these deleted elements, we wish to delete the existing test class.

Let’s suppose, in this case, that the TestModelUpdater class is a visitor that checks that the tested model is up-to-date (and that deletes elements that have to be deleted). It extends com.modeliosoft.modelio.api.model.utils.DefaultMetamodelVisitor and redefines the operation visitModelTree to remove class stereotyped JUnit without «JUnitDependency» dependency.

JUnitModelChangeHandler class

  1. Go to image implementation / image java / image org.sample.junit / image impl

  2. On image impl, create a image class: JUnitModelChangeHandler

  3. Select the image JUnitModelChangeHandler class, and open the Link Editor

  4. Create an inheritance link to org.modelio.api.model.change.IModelChangeHandler
    (Accept the "update model from the interface" suggestion)

  5. In the Java Designer property view, run image generate, then image edit

  6. Update the JUnitModelChangeHandler class with the following content:
    (JUnitModelChangeHandler.java source file)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package org.sample.junit.impl;

import java.util.ArrayList;
import java.util.List;
import com.modeliosoft.modelio.javadesigner.annotations.objid;
import org.modelio.api.modelio.model.IModelingSession;
import org.modelio.api.modelio.model.ITransaction;
import org.modelio.api.modelio.model.event.IElementDeletedEvent;
import org.modelio.api.modelio.model.event.IModelChangeEvent;
import org.modelio.api.modelio.model.event.IModelChangeHandler;
import org.modelio.vcore.smkernel.mapi.MObject;



public class JUnitModelChangeHandler implements IModelChangeHandler {

    public void handleModelChange(IModelingSession session, IModelChangeEvent event) {
        TestModelUpdater updater = new TestModelUpdater();

        // Memorize the parents to be updated
        List<MObject> parentsToUpdate = new ArrayList<>();
        for (IElementDeletedEvent deletedElement : event.getDeleteEvents()) {
            if (!parentsToUpdate.contains(deletedElement.getOldParent())) {
                parentsToUpdate.add(deletedElement.getOldParent());
            }
        }

        // Visit the elements to delete
        try (ITransaction t = session.createTransaction("Update the test hierarchy")) {
            boolean modelUpdated = false;
            for (MObject parent : parentsToUpdate) {
                if (((Boolean) parent.accept(updater)) == true) {
                    modelUpdated = true;
                }
            }

            // If the model has changed, the transaction is commited.
            if (modelUpdated) {
                t.commit();
            } else {
                t.rollback();
            }
        }
    }
}

TestModelUPdater class

  1. Go to image implementation / image java / image org.sample.junit / image impl

  2. On image impl, create a image class: TestModelUpdater

  3. Select image TestModelUpdater, then use the Link Editor to create an inheritance link to org.modelio.metamodel.visitors.DefaultModelVisitor

  4. In the Java Designer property view, run image generate, then image edit

  5. Update the TestModelUPdater class with the following content:
    (TestModelUpdater.java source file)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package org.sample.junit.impl;

import java.util.ArrayList;
import java.util.List;
import com.modeliosoft.modelio.javadesigner.annotations.objid;
import org.modelio.metamodel.uml.infrastructure.Dependency;
import org.modelio.metamodel.uml.infrastructure.Element;
import org.modelio.metamodel.uml.statik.Class;
import org.modelio.metamodel.uml.statik.Package;
import org.modelio.metamodel.visitors.DefaultModelVisitor;


public class TestModelUpdater extends DefaultModelVisitor {

    @Override
    public Object visitPackage(Package obj) {
        List<Class> toDelete = new ArrayList<>();

        for (Class c : obj.getOwnedElement(Class.class)) {
            // Check all owned test cases
            if (c.isStereotyped("JUnit", "JUnit")) {
                boolean hasTestedClass = false;
                // Check that there is a link towards a tested class
                for (Dependency dep : c.getDependsOnDependency()) {
                    if (dep.isStereotyped("JUnit", "JUnitDependency")) {
                        hasTestedClass = true;
                    }
                }

                // No tested class means the test case must be deleted
                if (hasTestedClass == false) {
                    toDelete.add(c);
                }
            }
        }

        if (toDelete.isEmpty()) {
            return Boolean.FALSE;
        } else {
            // Delete orphan test cases
            for (Class c : toDelete) {
                c.delete();
            }
            return Boolean.TRUE;
        }
    }

    @Override
    public Object visitElement(Element obj) {
        return Boolean.FALSE;
    }
}

JUnitLifeCycleHandler class

  1. Go to image implementation / image java / image org.sample.junit / image impl / image JUnitLifeCycleHandler

  2. In the Java Designer property view, run image generate, then image edit

  3. Update the start() and stop() methods with the following content:
    (JUnitLifeCycleHandler.java source file)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
public class JUnitLifeCycleHandler extends DefaultModuleLifeCycleHandler {

    private JUnitModelChangeHandler modelChangeHandler = null;
...
    @Override
    public boolean start() throws ModuleException {
        IModuleContext context = this.module.getModuleContext();
        IModelingSession session = context.getModelingSession();
        modelChangeHandler = new JUnitModelChangeHandler();
        session.addModelHandler(modelChangeHandler);
        return super.start();
    }

    @Override
    public void stop() throws ModuleException {
        IModuleContext context = this.module.getModuleContext();
        IModelingSession session = context.getModelingSession();
        session.removeModelHandler(modelChangeHandler);
        modelChangeHandler = null;
        super.stop();
    }
...

}

<< Processing commands: model transformation

Index

Creating a module property page >>