This repo is a ros-kinetic workspace with a package called example for the purposes of demonstrating rostest for both Python testing (using rosunit and unittest) and C++ (using gtest).
Additionally, it will demonstrate the integration of ROS with eProsima's Fast-RTPS (DDS).
The concept is as follows:
RTPS topic -> ROS topic -> ROS service
And so sequentially, it looks a bit like this:
- Node A publishes to an RTPS topic
- Node B subscribes to that RTPS topic and publishes to a ROS topic
- Node C subscribes to that ROS topic and hosts the data as a service
- Node D calls the service
The package is very contrived, it's comprised of the following:
- RTPS Messages
ExampleMessage
- ROS Messages
ExampleMessage
- ROS Services
ExampleService
- Nodes
- Relevant
rtps_topic_publisher.cpp(C++)- Publishes an RTPS
ExampleMessagemessage to RTPSExamplePubSubTopictopic with some fixed and random properties
- Publishes an RTPS
rtps_to_ros_gateway.cpp(C++)- Receives RTPS
ExampleMessagemessages from RTPSExamplePubSubTopictopic and publishes a ROSExampleMessagemessage to ROStopictopic
- Receives RTPS
topic_subscriber.py(Python)- Receives and logs ROS
ExampleMessagemessages from ROStopictopic
- Receives and logs ROS
service_hoster.py(Python)- Receives ROS
ExampleMessagemessages from ROStopictopic and catalogues them byfirst_nameandlast_name, exposing that catalogue via ROSExampleServiceservice
- Receives ROS
service_caller.py(Python)- Calls the ROS
ExampleServiceservice with thefirst_nameandlast_nameprovided on the command line
- Calls the ROS
- Deprecated (context only)
topic_publisher.py(Python)- Publishes a ROS
ExampleMessagemessage to ROStopictopic with some fixed and random properties
- Publishes a ROS
- Relevant
- Tests
ros_kinetic_catkin_example_test.cpp(C++)- Instantiates
rtps_topic_publisher.cpp,rtps_to_ros_gateway.cppandservice_hoster.pyand validates their behaviour (using C++gtest)
- Instantiates
ros_kinetic_catkin_example_test.py(Python)- Instantiates
rtps_topic_publisher.cpp,rtps_to_ros_gateway.cppandservice_hoster.pyand validates their behaviour (using Pythonunittest)
- Instantiates
Prerequisites:
- For local building
- ROS Kinetic
- A shell with
/opt/ros/kinetic/setup.bashsourced
- For building in Docker
- Docker
To build the package locally:
./local_build.sh
To test the package locally:
./local_test.sh
To build the package in Docker:
./build.sh
To test the package in Docker:
./test.sh
NOTE: The following stuff assumes you're operating locally and you've built the package already.
To manually build everything:
catkin_make && catkin_make_install && catkin_make tests
To individually spin up all the pieces (run each command in a separate terminal):
roscore
source devel/setup.bash; rosrun example example_rtps_topic_publisher
source devel/setup.bash; rosrun example example_rtps_to_ros_gateway
source devel/setup.bash; rosrun example topic_subscriber.py
source devel/setup.bash; rosrun example service_hoster.py
# and to call the service
source devel/setup.bash; rosrun example service_caller.py Edward Beech
To run the Python tests:
source devel/setup.bash; rostest example ros_kinetic_catkin_example_test_python.launch
Underneath, rostest seems to call rosunit which in turn calls unittest.
To run the C++ tests:
source devel/setup.bash; rostest example ros_kinetic_catkin_example_test_cpp.launch
Underneath, rostest seems to call rosunit which in turn calls gtest.
To run all the tests:
source devel/setup.bash; catkin_make run_tests && catkin_test_results --all
The catkin_test_results step is required to truthfully represent the test results, as catkin_make run_tests invokes rostest which swallows up test failures for some reason.
- Running
rostestgenerates bothrostestandrosunitjUnit XML test results in~/.ros/test_results/example- Only
rosunitresults seems to record failures when they happen,rostestrecords a single pass for all tests runs (regardless of outcome) - You can differentiate between results of the two kinds by jUnit XML file prefix (
rostestvsrosunit)
- Only
- In the
rostestjUnit XML, as mentioned above, there is a single<testcase>entry per<test>tag in the.launchfile, bearing the name specified intype=for the<test>tag - In the
rosunitjUnit XML, all the test methods appear named as they are in your test code
The above is demonstrated (for rosunit) through the naming of the methods in the test files, e.g.:
// C++ and gtest
TEST(SomeTestSuite, testRTPSTopicPublisherIdeT2859)
# Python and unittest
def test_topic_publisher_ide_t2857(self):
And also demonstrated (for rostest) through the naming of the <test> elements in the .launch files, e.g:
<test pkg="example" test-name="ros_kinetic_catkin_example_cpp_test_ide_t2855" type="example-test"/>
And finally represented in the jUnit XML test results; e.g.:
# for rostest (NOTE: all wrapped up into 1 test)
<testsuite errors="0" failures="0" name="unittest.suite.TestSuite" tests="1" time="2.633"><testcase classname="rostest.runner.RosTest" name="testros_kinetic_catkin_example_cpp_test_ide_t2855" time="2.6330" /
# for rosunit (NOTE: the 2 tests that are actually there)
<testsuites tests="2" failures="0" disabled="0" errors="0" timestamp="2019-05-15T11:22:43" time="0.485" name="AllTests">