bdd-dsl

Logo

Metamodels and associated toolings for applying Behaviour-Driven Development (BDD) to test robotic scenarios.

View the Project on GitHub secorolab/bdd-dsl

Tutorial: Modeling pick & place applications using RobBDD

This tutorial shows how to use the RobBDD Domain-Specific-Language (DSL) to model robotics acceptance criteria and generate Gherkin feature files for acceptance test execution of pick & place scenarios. The following model examples can also be found under the examples/models folder in the RobBDD repository.

[!TIP] A plugin is available for Neovim users with syntax highlighting and model checking.

Table of contents

  1. Specifying a scenario template
    1. Identifiers
    2. Minimal Scenario Template
    3. Adding fluent clauses
    4. Adding custom fluents
  2. Specifying a scene
    1. Adding objects and agents
    2. Composing workspaces
  3. Specifying user stories and scenario variants
    1. Table variation
    2. Cartesian product variation
  4. From pick and place to sorting
  5. References

Specifying a scenario template

Behaviour-Driven Development (BDD) in an acceptance testing approach which defines each acceptance, i.e., “good-enough”, criterion, as a scenario using the Given pre-condition, When action/event occurs, Then expect some outcome formulation. A popular BDD implmentation is Gherkin. When applied to a simple pick & place task, a Gherkin scenario may look like the following:

Scenario Outline: pickplace scenario
  Given <object> is located at <pick_ws>
  When the <robot> picks <object> and places it at <place_ws>
  Then the object is located at <place_ws>

  Examples:
    | robot  | object | pick_ws | place_ws |
    | kinova | ball   | table   | shelf    |
    ...

Here, in addition to the pick-and-place behaviour, we declare 2 expectations: the target object to be in the picking area before the behaviour, and at the placing area afterwards. <robot>, <object>,… in the excerpt are parameters that can take different values in the Examples table underneath. We now walk through how we can specify this using RobBDD. We begin with the following minimal example:

// pickplace.bdd
ns tutorial = "https://secorolab.github.io/robbdd/tutorials/"

Task (ns=tutorial) tsk-pickplace

Event (ns=tutorial) evt-scr-start
Event (ns=tutorial) evt-pick-start
Event (ns=tutorial) evt-place-end
Event (ns=tutorial) evt-scr-end

Scenario Template (ns=tutorial) pickplace-tmpl {
    duration: from <evt-scr-start> until <evt-scr-end>
    task: <tsk-pickplace>
    var robot
    var target-obj
    var place-ws

    When:
        Behaviour (ns=tutorial) pickplace-bhv {
            duration: from <evt-pick-start> until <evt-place-end>
            <robot> picks <target-obj> and places at <place-ws>
        }
}

This minimal scenario template can be used to generate to an RDF graph, e.g., in the turtle (TTL) format, using the following textx generate command, which will print the TTL serialization to the terminal:

textx generate pickplace.bdd --target console --format ttl

We now walk through the key elements in this example, before introducing additional specifications.

Identifiers

RDF graphs use Internationalized Resource Identifiers (IRIs) to identify nodes. To accommodate this during graph generation, we include the namespace declaration syntax ns tutorial='http://...', which different models can point to, e.g. with (ns=tutorial), to form complete IRIs. The tutorial string is also used as prefix to form compact IRIs. For example, (ns=tutorial) pickplace-tmpl will be transformed into a node with IRI https://secorolab.github.io/robbdd/tutorials/pickplace-tmpl, which has the short form tutorial:pickplace-tmpl. IRIs allow extending a model in the generated graph with any additional information as needed, as long as they refer the model’s IRI. In RobBDD, the element IDs, e.g., evt-scr-start, are combined with a namespace declaration, e.g., ns tutorial, to generate the full IRI in the graph. The namespace reference can either be explicit like with task or event declarations, or inherited from the parent like with variable declarations.

Minimal Scenario Template

A scenario template, defined with the Scenario Template (ns=...) ... { ... } block, outlines the basic BDD Given-When-Then structure described above. For a valid generation, a template require the following elements:

Adding fluent clauses

Fluent clauses specify expectations about the behaviour under test. A fluent clause, e.g. holds(...), can be added to the Given & Then parts of the scenario, and composes a predicate, e.g. is located at, a time constraint, e.g. before ..., with corresponding variables, e.g. <target-obj>. More details on this composition is described on the “concepts” page. A general language for predicates is not available at the moment. Available syntax:

The two “located at” clauses in the above Gherkin example can be reproduced in RobBDD with the following additions:

+++ pickplace.bdd
@@ -12,11 +12,18 @@
     task: <tsk-pickplace>
     var robot
     var target-obj
+    var pick-ws
     var place-ws

+    Given:
+        fc-located-before: holds(<target-obj> is located at <pick-ws>, before <evt-pick-start>)
+
     When:
         Behaviour (ns=tutorial) pickplace-bhv {
             duration: from <evt-pick-start> until <evt-place-end>
             <robot> picks <target-obj> and places at <place-ws>
         }
+
+    Then:
+        fc-located-after: holds(<target-obj> is located at <place-ws>, after <evt-place-end>)
 }

Adding custom fluents

In addition to the built-in fluents above, you can also add custom clauses using the pred("string template with {arg_name}", arg_name=<variable>) syntax:

+++ pickplace.bdd
@@ -25,5 +25,12 @@
         }

     Then:
-        fc-located-after: holds(<target-obj> is located at <place-ws>, after <evt-place-end>)
+        (
+            fc-located-after: holds(<target-obj> is located at <place-ws>, after <evt-place-end>)
+            and
+            fc-no-collide: holds(
+                pred("\"{robot}\" does not collide with \"{place_ws}\"", robot=<robot>, place_ws=<place-ws>),
+                from <evt-pick-start> until <evt-place-end>
+            )
+        )
 }

You can now run the textx generate command above again to see how the graph changes. Note that we cannot generate to Gherkin at this point since no scenario variant is specified.

Specifying a scene

To vary the above template, we must first specify the possible objects, workspaces, and agents that can appear in the scene. We will now walk through how a compose a scene for the above pick & place task using RobBDD syntax.

Adding objects and agents

At the minimum, a scene model requires a scene declaration, e.g., pickplace_scene in the example below. Here, we start with a set of objects that can be picked and placed by the robot:

// lab.scene
ns lab-env='https://secorolab.github.io/models/environments/secorolab/'

obj set (ns=lab-env) pickplace_objects {
  object red-cube,
  object blue-cube,
  object green-ball,
  object bottle
}

scene (ns=lab-env) pickplace_scene {
  obj set <pickplace_objects>
}

Generation to RDF graph, e.g., printing to the terminal in the turtle format, can be tested with:

textx generate lab.scene --target console --format ttl

Then we add a set of robots to test the pick & place behaviour with:

+++ lab.scene
@@ -7,7 +7,13 @@
   object green-ball,
   object bottle
 }
+agn set (ns=lab-env) lab_agents {
+  agent panda,
+  agent ur10,
+  agent kinova
+}

 scene (ns=lab-env) pickplace_scene {
   obj set <pickplace_objects>
+  agn set <lab_agents>
 }

Composing workspaces

We now specify the workspaces where the pick & place behaviour take place. For example, we want to describe the robot picking up objects from a table and put them into one of two containers, which are also on the table. RobBDD scene specification differentiates between physical objects that an agent can interact with and the abstract workspace where a behaviour may take place. One example of the distinction is, when we command the robot to the table workspace, we don’t mean on top of the table object, but any space surrounding it.

To this end, we first declare the objects and workspaces as follows:

+++ lab.scene
@@ -13,6 +13,17 @@
   agent kinova
 }

+obj set (ns=lab-env) ws_objects {
+  object container_1,
+  object container_2,
+  object dining_table
+}
+ws set (ns=lab-env) lab_workspaces {
+  workspace table_ws,
+  workspace container_1_ws,
+  workspace container_2_ws
+}
+
 scene (ns=lab-env) pickplace_scene {
   obj set <pickplace_objects>
   agn set <lab_agents>

Then we declare the compositions: table, containers workspaces linking to corresponding objects, table workspace linking to the container workspaces, and the scene linking to the composition:

+++ lab.scene
@@ -24,7 +24,20 @@
   workspace container_2_ws
 }

+comp (ns=lab-env) comp_container1 of ws <lab_workspaces.container_1_ws> {
+  obj <ws_objects.container_1>
+}
+comp (ns=lab-env) comp_container2 of ws <lab_workspaces.container_2_ws> {
+  obj <ws_objects.container_2>
+}
+comp (ns=lab-env) comp_table_pickplace of ws <lab_workspaces.table_ws> {
+  obj <ws_objects.dining_table>
+  ws comp <comp_container1>
+  ws comp <comp_container2>
+}
+
 scene (ns=lab-env) pickplace_scene {
   obj set <pickplace_objects>
+  ws comp <comp_table_pickplace>
   agn set <lab_agents>
 }

The above TextX generation command should work after each of the above changes. For reference, you can also download/view the complete lab.scene model. A scenario variant can now link to pickplace_scene, as described further in the next section.

Specifying user stories and scenario variants

With the scene composition available, we can now specify variants of the above scenario template. Here, a user story needs to be declared, e.g. (ns=bdd_var) us_pickplace below, as a collection of scenario variants, e.g. table_pick below. A scenario variant links to a template, a scene model, and declares variation of the scenario’s variables. Two types of variation are currently supported by RobBDD: table form like with Gherkin’s Examples table in a ScenarioOutline, and as the Cartesian products of sets of variable values.

Table variation

The table-style variation links to scenario variables in the header row, separated from the corresponding value rows by |---|. Each value rows will replace the variables in the scenario clauses, similar to Gherkin’s Examples table. Cell values can be links to scene elements, e.g. <pickplace_objects.red-cube>, links to set of elements, e.g. obj set <pickplace_objects>, or literal values like strings or numbers.

+++ pickplace.bdd
@@ -1,3 +1,5 @@
+import "lab.scene"
+
 ns tutorial = "https://secorolab.github.io/robbdd/tutorials/"

 Task (ns=tutorial) tutorial-tsk
@@ -34,3 +36,22 @@
             )
         )
 }
+
+User Story (ns=tutorial) pickplace-us {
+  As A "Function Developer"
+  I Want "Pick and place behaviour"
+  So That "I can transport objects"
+
+  Scenarios:
+    Scenario pickplace-table {
+        template: <pickplace-tmpl>
+        scene: <pickplace_scene>
+
+        variation:
+        | <pickplace-tmpl.target-obj> | <pickplace-tmpl.pick-ws> | <pickplace-tmpl.place-ws> | <pickplace-tmpl.robot> |
+        |---|
+        | <pickplace_objects.red-cube> | <lab_workspaces.table_ws> | <lab_workspaces.container_1_ws> |  <lab_agents.panda> |
+        | <pickplace_objects.green-ball> | <lab_workspaces.container_2_ws> | <lab_workspaces.table_ws> |  <lab_agents.ur10> |
+        | <pickplace_objects.bottle> | <lab_workspaces.table_ws> | <lab_workspaces.container_2_ws> | <lab_agents.kinova> |
+    }
+}

At this point you should be able to generate a Gherkin feature file using the following command:

textx generate pickplace.bdd --target gherkin

Cartesian product variation

In addition to the table syntax, a scenario variation can also be specified as the Cartesian product of sets of possible values:

+++ pickplace.bdd
@@ -9,6 +9,10 @@
 Event (ns=tutorial) evt-place-end
 Event (ns=tutorial) evt-scr-end

+const set (ns=tutorial) ws_set {
+    <lab_workspaces.table_ws>, <lab_workspaces.container_1_ws>
+}
+
 Scenario Template (ns=tutorial) pickplace-tmpl {
     duration: from <evt-scr-start> until <evt-scr-end>
     task: <tutorial-tsk>
@@ -54,4 +58,17 @@
         | <pickplace_objects.green-ball> | <lab_workspaces.container_2_ws> | <lab_workspaces.table_ws> |  <lab_agents.ur10> |
         | <pickplace_objects.bottle> | <lab_workspaces.table_ws> | <lab_workspaces.container_2_ws> | <lab_agents.kinova> |
     }
+
+    Scenario pickplace-cart {
+        template: <pickplace-tmpl>
+        scene: <pickplace_scene>
+
+        variation:
+            var <pickplace-tmpl.target-obj>: obj set <pickplace_objects>
+            var <pickplace-tmpl.pick-ws>: set <ws_set>
+            var <pickplace-tmpl.place-ws>: set <ws_set>
+            var <pickplace-tmpl.robot>: {
+                <lab_agents.panda>, <lab_agents.ur10>
+            }
+    }
 }

Here, value sets can be specified in several ways:

For reference, you can view/download the complete pickplace.bdd model.

[!IMPORTANT] The above syntax is only for non-set variable. Separate syntax is required for a set variable, as the collection of its possible values must be a set of sets. We describe this in more details in the following section.

From pick and place to sorting

In 1, we discussed the extensions needed for our BDD tool chain to represent an object sorting scenario, which can be considered as repeating the pick & place behaviours for multiple objects and workspaces. This section describes how this extensions can be specified with RobBDD. Following is a template for such a sorting scenario:

// sorting.bdd
...
Scenario Template (ns=tutorial) pickplace-tmpl {
    duration: from <evt-scr-start> until <evt-scr-end>
    task: <tsk-sorting>
    var robot
    set var target-objects
    var pick-ws
    set var place-workspaces

    for all ( var x in <target-objects> ) {
        Given:
            fc-located-before: holds(<x> is located at <pick-ws>, before <evt-pick-start>)

        When:
            Behaviour (ns=tutorial) pickplace-bhv {
                duration: from <evt-pick-start> until <evt-place-end>
                <robot> picks <x> and places at <place-workspaces>
            }

        Then:
            fc-located-after: holds(<x> is located at <place-workspaces>, after <evt-place-end>)
    }

    Then:
        fc-sorted: holds(<target-objects> are sorted into <place-workspaces>, after <evt-place-end>)
}

Here, we introduce set quantifiers and set variables to represent the sorting scenario:

Scenario isaac_sorting {
    template: <sorting-tmpl>
    scene: <pickplace_scene>

    variation:
        set var <sorting-tmpl.target-objects>: select 3 combinations from <pickplace_objects>
        var <sorting-tmpl.pick-ws>: {
            <lab_workspaces.table_ws>
        }
        set var <sorting-tmpl.place-workspaces>: {
            { <lab_workspaces.container_1_ws>, <lab_workspaces.container_2_ws> },
            { <lab_workspaces.container_2_ws>, <lab_workspaces.container_1_ws> }
        }
        var <sorting-tmpl.robot>: agn set <lab_agents>
}

In the scenario variant, variation of a set variable needs to resolve to a set of sets, and can be specified in 2 main ways:

For reference, you can view/download the complete sorting.bdd model.

References

  1. B. Hunecke, M. Nguyen, N. Hochgeschwender, S. Wrede, “Specification and Execution of Robotic Acceptance Tests for Object Sorting”, In: European Robotics Forum 2025. Springer Proceedings in Advanced Robotics, vol 36, June 2025.