Testing and Debugging in ROS and C++
Overview
Levels of ROS testing
Library-level unit tests
- These tests are independent of ROS and are used for testing individual functions and libraries.
- Each test should be small and (ideally) run quickly
- Ideally these tests are designed to run every time you compile (or at least every time immediately prior to merging your code into the master)
Single Node Unit Tests
- The goal is to validate the functionality of a single Node
- Test its publishers, subscribers, and servers, and parameter usage
- These tests should also be relatively small and self contained
- They can be set up to run as part of the compilation process
Integration Test
These tests start multiple nodes and test how the whole system works together
- These tests are made in the same way as single node unit tests, but launch more ROS nodes
- Some integration tests can be done automatically as part of the compilation process
- Some integration tests may require physical robot hardware
C++ Unit Testing Framework
There is no standard unit-testing framework for C++. We will use
Catch2 (version 3) in this class, but these
notes also discuss gtest.
Catch2
Overview
- Catch2 is a light-weight unit-testing framework for C++
- The Tutorial
- The Reference Documentation
- The catch_ros2 package enables the use of Catch2 with ROS 2
Quick Guide
- The catch_ros2 documentation provides full usage instructions.
- To do regular unit tests, right the Catch tests as usual, then link the executable with
target_link_libraries(mytests catch_ros2::catch_ros2_with_main)- This automatically provides a main for the test, but there are other options
- To do integration testing requires
- The nodes you want to test
- A Integration Testing Node
- This node uses Catch2 to run test the node
- A Launch File
- This node launches the nodes the node that should be tested and the test node itself
- Add
catch_ros2_add_integration_test(MyTest LAUNCH_FILE my_launchfile TIMEOUT 60)to CMake if you want the test to run withcolcon test.TIMEOUTdefaults to 60 and can be ommited, but it is useful to increase the timeout if running the test node in gdb- The test can also be run by running the test launch file
Google Test (GTest)
Overview
- The most common C++ unit testing framework in ROS is Google Test.
- Google Test Primer
- Google Test Advanced (Not too advanced for you)
- ROS 2 GTest Tutorial
Debugging
- Using
gdbcan greatly help you track down issues in your program- Among other features, it enables you to step through the code, examine variables, and break on certain lines
- use
ros2 run --prefix "gdb --args" package nodeto launch the node ingdb - Commands that may be useful are
tui enableto view a graphical interface with easier commandsrunstart running the program. Abbreviated withrcontinuecontinue running a paused program. Abbreviated withcbreak file:lineorbreak functionto put a breakpoint at a line or a function. Abbreviated withbnextgo to the next line, skipping over functions. Abbreviated withnstepgo to the next line, jumping into functions. Abbreviated withsprintdisplay the value of a variable or memory address
- To use
gdbmost effectively, the code must be compiled with Debugging symbols enabled- Use the
Debugbuild type inCMake. See CMake Notes for how to set debugging mode
- Use the
From Launchfiles
Launching a node in GDB from a launchfile requires extra consideration because you do not have access to a terminal for the node by default
(the ros2 launch command controls input from the terminal). Instead we will use gdbserver, which allows remote debugging
- Use
launch-prefixto startgdbserver:- XML Launch File:
<node ... launch-prefix'gdbserver :3000'>= - Python Launch File ~Node(…, prefix=['gdbserver :3000'])
- The
:3000is the port, the number is somewhat arbitrary (as long as it doesn't conflict with other open ports). - This same argument works when using the
<catch_ros2_node>tag
- XML Launch File:
- Run
gdband connect to the server usingremote target :3000(or the port you specified above)- You can now use
gdbas usual.
- You can now use
- Alternatively, you can use a prefix command to run
gdbin a new terminal window (e.g.,xterm).xterm -e gdb -ex run --args- This method is in some ways more straightforward: it will launch a new
xtermwindow - However, it is also dependent on the availability of a gui (e.g., will not work over ssh).
- In a pinch, you can comment out the start of the node in the launchfile and manually run it with
ros2 run