Install any skill in seconds. Free to start, no credit card required.
Get Started Free →ROS2 test strategies and patterns with Clean Architecture (Python & C++)
.claude/skills/harunkurtdev-ros2-testing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 13% | 0% |
| case-08 | ✗→✓ | ▲ Improved | -21% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 19% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 31% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 31% | 0% |
This skill provides test strategies for ROS2 applications adhering to Clean Architecture principles, covering Unit, Integration, and E2E tests in both Python and C++.
/\
/ \ E2E Tests (Launch Tests / System Tests)
/----\
/ \ Integration Tests (Node / Component Tests)
/--------\
/ \ Unit Tests (Domain / Application Logic)
/--------------\tests/
├── unit/
│ ├── domain/
│ └── application/
├── integration/
│ └── ros2/
└── e2e/
└── launch_tests/See the previous version for Python Unit Test examples. They remain valid as domain logic is pure Python.
python# tests/integration/ros2/nodes/test_sensor_node.py import pytest import rclpy from rclpy.node import Node from std_msgs.msg import Float64 @pytest.fixture(scope='module') def ros_context(): rclpy.init() yield rclpy.shutdown() @pytest.fixture def test_node(ros_context): node = Node('test_helper') yield node node.destroy_node() def test_sensor_integration(test_node): # Verify node behavior by subscribing/publishing pass
cpp// tests/unit/domain/use_cases/test_robot_controller.cpp #include <gtest/gtest.h> #include <gmock/gmock.h> #include "domain/use_cases/robot_controller.hpp" #include "domain/entities/robot_state.hpp" using namespace domain; using ::testing::Return; class MockRobotRepository : public repositories::IRobotRepository { public: MOCK_METHOD(entities::RobotState, get_state, (), (override)); MOCK_METHOD(void, set_mode, (entities::RobotMode), (override)); }; TEST(RobotControllerTest, StartFromIdle) { auto mock_repo = std::make_shared<MockRobotRepository>(); use_cases::RobotControllerUseCase use_case(mock_repo); EXPECT_CALL(*mock_repo, get_state()) .WillOnce(Return(entities::RobotState{entities::RobotMode::IDLE})); EXPECT_CALL(*mock_repo, set_mode(entities::RobotMode::ACTIVE)); auto result = use_case.start(); EXPECT_TRUE(result.success); }
cpp// tests/integration/ros2/test_sensor_node.cpp #include <gtest/gtest.h> #include <rclcpp/rclcpp.hpp> #include "infrastructure/ros2/nodes/sensor_node.hpp" class SensorNodeTest : public ::testing::Test { protected: void SetUp() override { rclcpp::init(0, nullptr); node_ = std::make_shared<infrastructure::ros2::nodes::SensorNode>(); } void TearDown() override { rclcpp::shutdown(); } std::shared_ptr<infrastructure::ros2::nodes::SensorNode> node_; }; TEST_F(SensorNodeTest, Initialization) { EXPECT_STREQ(node_->get_name(), "sensor_node"); }
You can run GTest executables from launch files to perform system-level tests.
python# tests/e2e/launch_tests/system_test.launch.py from launch import LaunchDescription from launch_ros.actions import Node from launch_testing.actions import ReadyToTest def generate_launch_description(): # Launch system under test app_node = Node(package='my_robot', executable='main_node') # Launch GTest runner test_runner = Node( package='my_robot', executable='system_integration_test', output='screen' ) return LaunchDescription([ app_node, test_runner, ReadyToTest() ])
unittest.mock for Python and gmock for C++.pytest.fixture and GTest SetUp/TearDown to manage ROS2 context (rclpy.init/shutdown).rclcpp::spin_some or wait_for_future to handle async operations.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 17,774 | 13,493 | -24% | 1 | 1 | 0% | 3,146 | 3,597 | +14% | 0 | 0 | — |
case-02 | fail→fail | 17,233 | 13,745 | -20% | 1 | 1 | 0% | 3,198 | 3,665 | +15% | 0 | 0 | — |
case-03 | pass→pass | 14,553 | 10,637 | -27% | 1 | 1 | 0% | 2,365 | 3,099 | +31% | 0 | 0 | — |
case-04 | fail→fail | 18,092 | 13,428 | -26% | 1 | 1 | 0% | 3,365 | 3,623 | +8% | 0 | 0 | — |
case-05 | pass→pass | 13,250 | 11,532 | -13% | 1 | 1 | 0% | 2,658 | 3,470 | +31% | 0 | 0 | — |
case-06 | pass→pass | 10,723 | 10,769 | +0% | 1 | 1 | 0% | 1,999 | 3,216 | +61% | 0 | 0 | — |
case-07 | fail→pass | 11,009 | 5,909 | -46% | 1 | 1 | 0% | 1,900 | 2,144 | +13% | 0 | 0 | — |
case-08 | fail→pass | 11,721 | 3,263 | -72% | 1 | 1 | 0% | 2,190 | 1,735 | -21% | 0 | 0 | — |
case-09 | pass→pass | 11,841 | 5,722 | -52% | 1 | 1 | 0% | 1,992 | 2,097 | +5% | 0 | 0 | — |
case-10 | pass→pass | 10,263 | 7,170 | -30% | 1 | 1 | 0% | 1,934 | 2,546 | +32% | 0 | 0 | — |
case-11 | pass→pass | 3,989 | 2,910 | -27% | 1 | 1 | 0% | 698 | 1,603 | +130% | 0 | 0 | — |
case-12 | pass→pass | 12,369 | 5,008 | -60% | 1 | 1 | 0% | 2,198 | 2,016 | -8% | 0 | 0 | — |
case-13 | pass→pass | 11,653 | 9,813 | -16% | 1 | 1 | 0% | 2,095 | 2,865 | +37% | 0 | 0 | — |
case-14 | pass→pass | 5,973 | 2,859 | -52% | 1 | 1 | 0% | 1,005 | 1,600 | +59% | 0 | 0 | — |
case-15 | pass→pass | 14,699 | 9,433 | -36% | 1 | 1 | 0% | 2,167 | 2,619 | +21% | 0 | 0 | — |
case-16 | fail→pass | 9,659 | 5,357 | -45% | 1 | 1 | 0% | 1,690 | 2,007 | +19% | 0 | 0 | — |
case-17 | pass→pass | 10,012 | 5,639 | -44% | 1 | 1 | 0% | 1,783 | 2,077 | +16% | 0 | 0 | — |
case-18 | pass→pass | 12,185 | 5,705 | -53% | 1 | 1 | 0% | 2,036 | 2,107 | +3% | 0 | 0 | — |
case-19 | pass→pass | 10,898 | 4,385 | -60% | 1 | 1 | 0% | 1,849 | 1,924 | +4% | 0 | 0 | — |
case-20 | pass→pass | 15,067 | 15,524 | +3% | 1 | 1 | 0% | 2,394 | 3,827 | +60% | 0 | 0 | — |
case-21 | pass→pass | 15,501 | 16,246 | +5% | 1 | 1 | 0% | 2,844 | 3,970 | +40% | 0 | 0 | — |
case-22 | pass→pass | 19,802 | 13,432 | -32% | 1 | 1 | 0% | 1,839 | 3,795 | +106% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +14 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.