Python unit testing
Contents
See also:
Python unit tests use the built-in unittest module with a small wrapper to enable XML output of the test results. In other words, Python unittest-based tests are compatible with ROS as long as you add an extra wrapper that creates the necessary XML results output.
There are two style of tests that are supported:
ROS-Node-Level Integration Tests: in general, these test against the external topic/service API of a Node. For these tests, it is assumed that your test script is itself a node.
Code-Level Unit Tests: in general, these tests make direct calls into your code; i.e. these are typical unit tests. Your test script is not a node.
The syntax for running these tests is different, so it's important that you properly distinguish between the two. Node-level tests will incur additional ROS resources to test whereas code-level tests are more lightweight.
ROS-Node-Level Integration Tests
Writing a unit test that acts as a ROS node is fairly simple in Python. You just have to wrap your code using the Python unittest framework.
http://docs.python.org/library/unittest.html
unittest code
A bare-bones test results looks as follows:
1 #!/usr/bin/env python
2 PKG = 'test_roslaunch'
3 import roslib; roslib.load_manifest(PKG)
4
5 import sys
6 import unittest
7
8 ## A sample python unit test
9 class TestBareBones(unittest.TestCase):
10 ## test 1 == 1
11 def test_one_equals_one(self):
12 self.assertEquals(1, 1, "1!=1")
13
14 if __name__ == '__main__':
15 import rostest
16 rostest.rosrun(PKG, 'test_bare_bones', TestBareBones)
NOTE: PKG should be the name of your package, and your package will need to depend on 'rostest' in the manifest.xml.
Almost everything there should be familiar to ROS developers as well as unittest writers. Everything except for the first and last two lines are a standard unittest. The first two lines are the standard ROS-python boilerplate for setting up your python path.
IMPORTANT: As this test is meant to be a ROS node launched via rostest, we need to use the rostest wrapper in our main:
The parameters to rostest.rosrun() are:
1 rostest.rosrun(package_name, test_name, test_case_class, sysargs=None, coverage_packages=None)
package_name
- Name of ROS package to record these results as. Test results are aggregated by package name.
test_name
- Name to use for test. This name will be used in the filename of the test results as well as in the XML results reporting.
sysargs
Override sys.argv.
coverage_packages=['module1.foo', 'module2.bar']
- List of packages that should be included in coverage report.
The rostest.rosrun method assumes that your test is a rospy node and will perform extra operations to try and make sure that your node properly runs and is shut down.
package_name and test_name control where your XML test results are placed (i.e. $ROS_ROOT/test/test_results/''package_name''/TEST-''test_name''.xml). rostest also examines sys.argv for command-line arguments such as --text and --gtest_output.
Update your manifest.xml
You will need to depend on the rostest package in order to setup your Python path correctly. Edit your manifest.xml to add:
<depend package="rostest" />
Create a rostest file
You will need to run your node in a rostest file. The rostest tool is based on roslaunch, so this is similar to writing a roslaunch file for your test. Please see the rostest documentation on how to integrate your node into an integration test.
Code-level Python Unit Tests
You can also write normal Python unit tests with ROS based on the Python unittest framework.
http://docs.python.org/library/unittest.html
unittest code
New in Diamondback
A bare-bones test results looks as follows:
1 #!/usr/bin/env python
2 PKG='test_foo'
3 import roslib; roslib.load_manifest(PKG)
4
5 import sys
6 import unittest
7
8 ## A sample python unit test
9 class TestBareBones(unittest.TestCase):
10
11 def test_one_equals_one(self):
12 self.assertEquals(1, 1, "1!=1")
13
14 if __name__ == '__main__':
15 import rosunit
16 rosunit.unitrun(PKG, 'test_bare_bones', TestBareBones)
This is almost identical to a standard Python unittest. At the very top, you need to invoke roslib.load_manifest to setup your Python path. The last two lines wrap your unit test with rosunit in order to produce XML test results:
The parameters to rosunit.unitrun() are:
1 rosunit.unitrun(package_name, test_name, test_case_class, sysargs=None, coverage_packages=None)
package_name
- Name of ROS package to record these results as. Test results are aggregated by package name.
test_name
- Name to use for test. This name will be used in the filename of the test results as well as in the XML results reporting.
sysargs
Override sys.argv.
coverage_packages=['module1.foo', 'module2.bar']
- List of packages that should be included in coverage report.
Running tests as part of 'make test'/CMakeLists.txt
You can have ROS automatically run these tests when you type make test by adding the following to your CMakeLists.txt:
rosbuild_add_pyunit(path/to/my_test.py)
Update your manifest.xml
You will need to depend on the rosunit package in order to setup your Python path correctly. Edit your manifest.xml to add:
<depend package="rosunit" />
Important Tips
1. Make sure to mark your Python script as executable if it is a ROS node. You may need to use the SVN command
svn propset svn:executable ON yourscript
PyUnit documentation can be found here http://pyunit.sourceforge.net/pyunit.html






