This package contains the base classes used to create new Behavior plugins that can be executed as part of Objectives.
Refer to the Behavior Trees documentation for more information.
Testing
The test
directory contains various helper classes and macros for Behavior testing. These helpers can:
- Initialize a test Behavior
- Provide accessors to various components: the Behavior, its node, its shared resources and mock logger if any, etc.
- Automatically test that the Behavior creates exactly a set of input and output ports.
- Automatically generate tests that check if the Behavior handles well a situation in which one input port is not set in the blackboard.
The test file is the source of truth about the Behavior, and the Behavior code will have to comply with the port definition in the test file, so the first thing to do is defining the input and output ports of the Behavior under test.
The following example defines a map of input ports named "myExpectedInputPorts" and a map of output ports named "myExpectedOutputPorts". In each line of the map definition, a port ID is associated with the value the port will take in that map. Note that, if a port is both an input and an output, we just need to include it in both maps.
BEGIN_BEHAVIOR_PORT_SETTER_MAP(myExpectedInputPorts)
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDPointCloud, sensor_msgs::msg::PointCloud2())
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDMasks3D, std::vector<Mask3D>())
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDBaseFrame, "")
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDPlaneInlierThreshold, 0.0)
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDMinimumFaceArea, 0.0)
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDFaceSeparationThreshold, 0.0)
END_BEHAVIOR_PORT_SETTER_MAP()
BEGIN_BEHAVIOR_PORT_SETTER_MAP(myExpectedOutputPorts)
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDGraspableObjects, GraspableObject())
END_BEHAVIOR_PORT_SETTER_MAP()
Now, to test the Behavior, our test fixture will have to inherit from a helper class (in addition to its base test fixture). There are helper classes for Behaviors with and without context, also known as shared resources. If the Behavior has context, the most common case, our fixture must inherit from WithBehavior<>
. In either case, we then must call initBehavior()
from the fixture's SetUp()
, or from any other setup function the fixture offers. The following example shows this case. Note how initBehavior()
gets the expected input and output ports we defined above.
#include <moveit_studio_behavior_interface/test/test_behavior.hpp>
...
class MyBehaviorWithContextTest
: public RosTest, public WithBehavior<MyBehaviorWithContext>
{
public:
void SetUp() override {
initBehavior("MyBehaviorWithContext", myExpectedInputPorts, myExpectedOutputPorts);
}
};
For behaviors without context, the pattern is the same, but uses the WithBehaviorWithoutContext
helper class instead.
#include <moveit_studio_behavior_interface/test/test_behavior.hpp>
...
class MyBehaviorWithoutContextTest
: public RosTest, public WithBehaviorWithoutContext<MyBehaviorWithoutContext>
{
public:
void SetUp() override {
initBehavior("MyBehaviorWithoutContext", myExpectedInputPorts, myExpectedOutputPorts);
}
};
There are macros to automatically generate tests to check if your Behavior handles the situation in which one port has no value set in the blackboard when it is run. The macros come in two flavors: for synchronous and asynchronous Behaviors. Regardless of the flavor, these macros can only generate tests for Behaviors with context.
If the Behavior is synchronous:
#include <moveit_studio_behavior_interface/test/test_behavior_input_port_not_set.hpp>
...
INSTANTIATE_SYNC_BEHAVIOR_PORT_NOT_SET_TESTS(MyBehaviorWithContext, myExpectedInputPorts,
myExpectedOutputPorts)
If the Behavior is asynchronous:
#include <moveit_studio_behavior_interface/test/test_behavior_input_port_not_set.hpp>
...
INSTANTIATE_ASYNC_BEHAVIOR_PORT_NOT_SET_TESTS(MyBehaviorWithContext, myExpectedInputPorts,
myExpectedOutputPorts)
Let's put everything together in an example where we test an asynchronous Behavior with context:
#include <moveit_studio_test_utils/ros_test.hpp>
#include <moveit_studio_behavior_interface/test/test_behavior.hpp>
#include <moveit_studio_behavior_interface/test/test_behavior_input_port_not_set.hpp>
namespace {
using ::moveit_studio::test_utils::WithBehavior;
}
BEGIN_BEHAVIOR_PORT_SETTER_MAP(myExpectedInputPorts)
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDPointCloud, sensor_msgs::msg::PointCloud2())
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDMasks3D, std::vector<Mask3D>())
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDBaseFrame, "")
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDPlaneInlierThreshold, 0.0)
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDMinimumFaceArea, 0.0)
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDFaceSeparationThreshold, 0.0)
END_BEHAVIOR_PORT_SETTER_MAP()
BEGIN_BEHAVIOR_PORT_SETTER_MAP(myExpectedOutputPorts)
DEFINE_BEHAVIOR_PORT_SETTER(kPortIDGraspableObjects, GraspableObject())
END_BEHAVIOR_PORT_SETTER_MAP()
class MyBehaviorWithContextTest
: public RosTest, public WithBehavior<MyBehaviorWithContext>
{
public:
void SetUp() override {
initBehavior("MyBehaviorWithContext", myExpectedInputPorts, myExpectedOutputPorts);
}
};
INSTANTIATE_ASYNC_BEHAVIOR_PORT_NOT_SET_TESTS(MyBehaviorWithContext, myExpectedInputPorts,
myExpectedOutputPorts)
}
Definition: action_client_behavior_base.hpp:16