Documentation Index Fetch the complete documentation index at: https://docs.roboticks.io/llms.txt
Use this file to discover all available pages before exploring further.
Writing tests in pytest
Pytest is the canonical way to write Python tests for Roboticks. The roboticks package adds four decorators and a set of rclpy-aware assertion helpers on top of stock pytest. Nothing else changes — your fixtures, your conftest.py, your parametrize idioms keep working.
The four decorators
Decorator Purpose Example @confirms(*req_ids)Records which requirement IDs this test verifies @confirms("REQ-014", "REQ-022")@tags(*tags)Free-form labels for filtering (e.g. smoke, nightly) @tags("nightly", "perception")@deadline(milliseconds=int)Fails the test if it exceeds the wall-clock budget @deadline(milliseconds=100)@requires_sim(engine, *, gpu)Routes the test to a sim-capable runner @requires_sim("gazebo", gpu=True)
All four stack and can decorate the same test in any order. See the SDK decorator reference for full semantics.
from roboticks import confirms, tags, deadline, requires_sim
@confirms ( "REQ-014" , "REQ-022" )
@tags ( "nightly" , "navigation" )
@requires_sim ( "gazebo" , gpu = True )
@deadline ( milliseconds = 2000 )
def test_robot_avoids_static_obstacle ():
...
Decorators are inert without the platform. Locally they only mark functions. The pytest plugin reads them at collection time and writes them into JUnit XML on session-end. See Pytest plugin .
Rclpy assertion helpers
The SDK ships ROS2-aware assertions in roboticks.assertions. They spin a node briefly, wait for the predicate, and raise an informative AssertionError on timeout. The helpers are guarded : importing the module on a host without rclpy installed raises a clear RuntimeError instead of an opaque ImportError.
from roboticks.assertions import (
assert_topic_published,
assert_service_response,
assert_action_result,
assert_param_equals,
assert_tf_transform,
)
Full signatures live in the SDK assertion reference .
Subscribe-and-assert on /cmd_vel
import pytest
from geometry_msgs.msg import Twist
from roboticks import confirms, deadline
from roboticks.assertions import assert_topic_published
@confirms ( "REQ-007" )
@deadline ( milliseconds = 5000 )
def test_teleop_publishes_cmd_vel ( ros_context ):
# ros_context is your conftest fixture that has rclpy.init()'d
msg = assert_topic_published(
topic = "/cmd_vel" ,
msg_type = Twist,
within = 3.0 , # seconds
predicate = lambda m : m.linear.x > 0.1 ,
)
assert msg.linear.x > 0.1
assert msg.angular.z == pytest.approx( 0.0 , abs = 0.01 )
Service call response
from example_interfaces.srv import AddTwoInts
from roboticks import confirms
from roboticks.assertions import assert_service_response
@confirms ( "REQ-019" )
def test_add_two_ints_service ( ros_context ):
request = AddTwoInts.Request( a = 2 , b = 3 )
response = assert_service_response(
service = "/add_two_ints" ,
srv_type = AddTwoInts,
request = request,
within = 2.0 ,
)
assert response.sum == 5
Action result
from nav2_msgs.action import NavigateToPose
from roboticks import confirms, requires_sim
from roboticks.assertions import assert_action_result
@confirms ( "REQ-031" )
@requires_sim ( "gazebo" )
def test_nav_to_pose_reaches_goal ( ros_context ):
goal = NavigateToPose.Goal()
goal.pose.pose.position.x = 2.0
result = assert_action_result(
action = "/navigate_to_pose" ,
action_type = NavigateToPose,
goal = goal,
within = 45.0 ,
)
assert result.result.error_code == 0
Conftest pattern
Spin up rclpy once per test session, and once per test give every test a clean executor. This pattern works for unit-scope ROS tests; for system tests use launch_testing instead.
# conftest.py
import pytest
import rclpy
from rclpy.executors import SingleThreadedExecutor
@pytest.fixture ( scope = "session" , autouse = True )
def _ros ():
rclpy.init()
yield
rclpy.shutdown()
@pytest.fixture
def ros_context ():
executor = SingleThreadedExecutor()
yield executor
executor.shutdown()
For tests that need a node-under-test running, layer it on:
@pytest.fixture
def perception_node ( ros_context ):
from my_pkg.perception_node import PerceptionNode
node = PerceptionNode()
ros_context.add_node(node)
yield node
node.destroy_node()
Parametrize and @confirms
@confirms is per-test-function, not per-test-id. Parametrized variants all confirm the same requirement set — which is usually what you want:
import pytest
from roboticks import confirms
@confirms ( "REQ-042" )
@pytest.mark.parametrize ( "velocity" , [ 0.1 , 0.5 , 1.0 , 2.0 ])
def test_velocity_clamp ( velocity ):
...
If a parametrized branch should confirm a different requirement, split it into two test functions. Decorators are deliberately not parametrize-aware — that ambiguity is what got teams in trouble with older tools.
What the plugin emits
Per test, in the JUnit XML:
< testcase name = "test_estop_halts_motion" classname = "tests.test_estop" time = "0.082" >
< properties >
< property name = "roboticks.confirms" value = "REQ-001,REQ-014" />
< property name = "roboticks.tags" value = "safety,smoke" />
< property name = "roboticks.deadline_ms" value = "100" />
</ properties >
</ testcase >
See Wire contract for the full schema and version handshake.
Next
Fault injection Drop topics, delay messages, kill nodes — under a context manager.
MCAP capture Record bag files per test for failure forensics.
Launch testing System tests that bring up multiple nodes.
Decorator reference Full signatures and edge cases.