ROS-----rostest tool使用
rostest tool是ROS提供的测试工具,其实质是roslaunch tool的功能扩展,但是使用rostest测试时候通常rostest运行的文件是扩展名为.test的XML格式文件,且文件内包含<test> tag。
注意:roslaunch可以运行扩展名为.test的文件但是不会执行<test>标签内的代码(即测试node不会启动)。
下面主要介绍rostest使用步骤,并借用ROS一些tutorial代码辅助进行分析。
1 编写test node
ROS中test node编写支持用2种框架(C++ gtest框架和python unittest框架)来编写code,需要注意的是对于用gtest编写的code是100%支持,用python unittest编写的code需要稍微封装一下。以下分别简要介绍2种编写test node的方法。
1.1 使用gtest编写test node
关于google test详细用法请参考:https://github.com/google/googletest
通常是在要测试package目录下创建1个子目录test,在test目录下添加测试代码。
(1)简单函数调用方式
#include "test_common.h"
#include <tf/transform_listener.h>// TEST CASES
TEST_F(DiffDriveControllerTest, testForward)
{// wait for ROSwhile(!isControllerAlive()){ros::Duration(0.1).sleep();}// zero everything before testgeometry_msgs::Twist cmd_vel;cmd_vel.linear.x = 0.0;cmd_vel.angular.z = 0.0;publish(cmd_vel);ros::Duration(0.1).sleep();// get initial odomnav_msgs::Odometry old_odom = getLastOdom();// send a velocity command of 0.1 m/scmd_vel.linear.x = 0.1;publish(cmd_vel);// wait for 10sros::Duration(10.0).sleep();nav_msgs::Odometry new_odom = getLastOdom();// check if the robot traveled 1 meter in XY plane, changes in z should be ~~0const double dx = new_odom.pose.pose.position.x - old_odom.pose.pose.position.x;const double dy = new_odom.pose.pose.position.y - old_odom.pose.pose.position.y;const double dz = new_odom.pose.pose.position.z - old_odom.pose.pose.position.z;EXPECT_NEAR(sqrt(dx*dx + dy*dy), 1.0, POSITION_TOLERANCE);EXPECT_LT(fabs(dz), EPS);// convert to rpy and test that waydouble roll_old, pitch_old, yaw_old;double roll_new, pitch_new, yaw_new;tf::Matrix3x3(tfQuatFromGeomQuat(old_odom.pose.pose.orientation)).getRPY(roll_old, pitch_old, yaw_old);tf::Matrix3x3(tfQuatFromGeomQuat(new_odom.pose.pose.orientation)).getRPY(roll_new, pitch_new, yaw_new);EXPECT_LT(fabs(roll_new - roll_old), EPS);EXPECT_LT(fabs(pitch_new - pitch_old), EPS);EXPECT_LT(fabs(yaw_new - yaw_old), EPS);EXPECT_NEAR(fabs(new_odom.twist.twist.linear.x), cmd_vel.linear.x, EPS);EXPECT_LT(fabs(new_odom.twist.twist.linear.y), EPS);EXPECT_LT(fabs(new_odom.twist.twist.linear.z), EPS);EXPECT_LT(fabs(new_odom.twist.twist.angular.x), EPS);EXPECT_LT(fabs(new_odom.twist.twist.angular.y), EPS);EXPECT_LT(fabs(new_odom.twist.twist.angular.z), EPS);
}TEST_F(DiffDriveControllerTest, testTurn)
{// wait for ROSwhile(!isControllerAlive()){ros::Duration(0.1).sleep();}// zero everything before testgeometry_msgs::Twist cmd_vel;cmd_vel.linear.x = 0.0;cmd_vel.angular.z = 0.0;publish(cmd_vel);ros::Duration(0.1).sleep();// get initial odomnav_msgs::Odometry old_odom = getLastOdom();// send a velocity commandcmd_vel.angular.z = M_PI/10.0;publish(cmd_vel);// wait for 10sros::Duration(10.0).sleep();nav_msgs::Odometry new_odom = getLastOdom();// check if the robot rotated PI around z, changes in the other fields should be ~~0EXPECT_LT(fabs(new_odom.pose.pose.position.x - old_odom.pose.pose.position.x), EPS);EXPECT_LT(fabs(new_odom.pose.pose.position.y - old_odom.pose.pose.position.y), EPS);EXPECT_LT(fabs(new_odom.pose.pose.position.z - old_odom.pose.pose.position.z), EPS);// convert to rpy and test that waydouble roll_old, pitch_old, yaw_old;double roll_new, pitch_new, yaw_new;tf::Matrix3x3(tfQuatFromGeomQuat(old_odom.pose.pose.orientation)).getRPY(roll_old, pitch_old, yaw_old);tf::Matrix3x3(tfQuatFromGeomQuat(new_odom.pose.pose.orientation)).getRPY(roll_new, pitch_new, yaw_new);EXPECT_LT(fabs(roll_new - roll_old), EPS);EXPECT_LT(fabs(pitch_new - pitch_old), EPS);EXPECT_NEAR(fabs(yaw_new - yaw_old), M_PI, ORIENTATION_TOLERANCE);EXPECT_LT(fabs(new_odom.twist.twist.linear.x), EPS);EXPECT_LT(fabs(new_odom.twist.twist.linear.y), EPS);EXPECT_LT(fabs(new_odom.twist.twist.linear.z), EPS);EXPECT_LT(fabs(new_odom.twist.twist.angular.x), EPS);EXPECT_LT(fabs(new_odom.twist.twist.angular.y), EPS);EXPECT_NEAR(fabs(new_odom.twist.twist.angular.z), M_PI/10.0, EPS);
}TEST_F(DiffDriveControllerTest, testOdomFrame)
{// wait for ROSwhile(!isControllerAlive()){ros::Duration(0.1).sleep();}// set up tf listenertf::TransformListener listener;ros::Duration(2.0).sleep();// check the odom frame existEXPECT_TRUE(listener.frameExists("odom"));
}int main(int argc, char** argv)
{ testing::InitGoogleTest(&argc, argv);ros::init(argc, argv, "diff_drive_test");ros::AsyncSpinner spinner(1);spinner.start(); int ret = RUN_ALL_TESTS();spinner.stop();ros::shutdown();return ret;
}
(2)处理exception
由于gtest并没有处理exception的机制,所以如果你测试的代码会throw exception,你应该自己添加try/catch来捕获exception,然后适当使用宏ADD_FAILURE 或者FAIL 。宏ADD_FAILURE 表示non-fatal失效, 而FAIL表示fatal失效。
#include <stdexcept> // for std::runtime_error
#include <gtest/gtest.h>
#include "map_server/image_loader.h"
#include "test_constants.h"/* Try to load a valid PNG file. Succeeds if no exception is thrown, and if* the loaded image matches the known dimensions and content of the file.** This test can fail on OS X, due to an apparent limitation of the* underlying SDL_Image library. */
TEST(MapServer, loadValidPNG)
{try{std_srvs::StaticMap::response map_resp;map_server::loadMapFromFile(&map_resp, g_valid_png_file, g_valid_image_res, false);EXPECT_FLOAT_EQ(map_resp.map.resolution, g_valid_image_res);EXPECT_EQ(map_resp.map.width, g_valid_image_width);EXPECT_EQ(map_resp.map.height, g_valid_image_height);for(unsigned int i=0; i < map_resp.map.width * map_resp.map.height; i++)EXPECT_EQ(g_valid_image_content[i], map_resp.map.data[i]);}catch(...){ADD_FAILURE() << "Uncaught exception : " << "This is OK on OS X";}
}/* Try to load a valid BMP file. Succeeds if no exception is thrown, and if
* the loaded image matches the known dimensions and content of the file. */
TEST(MapServer, loadValidBMP)
{try{std_srvs::StaticMap::response map_resp;map_server::loadMapFromFile(&map_resp, g_valid_bmp_file, g_valid_image_res, false);EXPECT_FLOAT_EQ(map_resp.map.resolution, g_valid_image_res);EXPECT_EQ(map_resp.map.width, g_valid_image_width);EXPECT_EQ(map_resp.map.height, g_valid_image_height);for(unsigned int i=0; i < map_resp.map.width * map_resp.map.height; i++)EXPECT_EQ(g_valid_image_content[i], map_resp.map.data[i]);}catch(...){ADD_FAILURE() << "Uncaught exception";}
}/* Try to load a valid BMP file. Succeeds if no exception is thrown, and if* the loaded image matches the known dimensions and content of the file. */
TEST(MapServer, loadValidBMP)
{try{std_srvs::StaticMap::response map_resp;map_server::loadMapFromFile(&map_resp, g_valid_bmp_file, g_valid_image_res, false);EXPECT_FLOAT_EQ(map_resp.map.resolution, g_valid_image_res);EXPECT_EQ(map_resp.map.width, g_valid_image_width);EXPECT_EQ(map_resp.map.height, g_valid_image_height);for(unsigned int i=0; i < map_resp.map.width * map_resp.map.height; i++)EXPECT_EQ(g_valid_image_content[i], map_resp.map.data[i]);}catch(...){ADD_FAILURE() << "Uncaught exception";}
}/* Try to load an invalid file. Succeeds if a std::runtime_error exception
* is thrown. */
TEST(MapServer, loadInvalidFile)
{try{std_srvs::StaticMap::response map_resp;map_server::loadMapFromFile(&map_resp, "foo", 0.1, false);}catch(std::runtime_error &e){ SUCCEED();return;}catch(...){ FAIL() << "Uncaught exception";}ADD_FAILURE() << "Didn't throw exception as expected";
}int main(int argc, char **argv)
{testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();
}
1.2 使用python unittest编写test node
ROS下使用python unittest框架测试,分为2种情况(ROS-Node-Level集成测试和Code-Level python单元测试)。
(1)ROS-Node-Level集成测试
以下文件test_talker.py,创建test node( test_talker ) ,测试rospy_tutorials下的talker节点的测试code。
#!/usr/bin/env pythonimport sys, unittest, time
import rospy, rostest
from std_msgs.msg import Stringclass TestTalker(unittest.TestCase):def __init__(self, *args):super(TestTalker, self).__init__(*args)self.success = Falsedef callback(self, data):self.success = data.data and data.data.startswith('hello world')def test_talker(self):rospy.init_node('test_talker')rospy.Subscriber("chatter", String, self.callback)timeout_t = time.time() + 10.0while (not rospy.is_shutdown() andnot self.success and time.time() < timeout_t):time.sleep(0.1)self.assert_(self.success)if __name__ == '__main__':rostest.rosrun('basics', 'talker_test', TestTalker, sys.argv)
说明:
rostest.rosrun(package_name, test_name, test_case_class, sysargs=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.
(2) Code-Level python单元测试
以下代码来源nodelet的test/test_loader.py
import roslib; roslib.load_manifest('test_nodelet')
import rospy
from std_msgs.msg import Float64
import unittest
import rostest
import randomclass PlusTester:def __init__(self, topic_in, topic_out, delta):self.pub = rospy.Publisher(topic_in, Float64)self.sub = rospy.Subscriber(topic_out, Float64, self.callback)self.expected_delta = deltaself.send_value = random.random()self.recieved = Falseself.result = Falseself.delta = deltadef run(self, cycles = 10):for i in range(1, cycles):self.pub.publish(Float64(self.send_value))rospy.loginfo("Sent %f"%self.send_value);if self.recieved == True:breakrospy.sleep(1.0)return self.result def callback(self, data):rospy.loginfo(rospy.get_name()+" I heard %s which was a change of %f",data.data, data.data-self.send_value)if data.data == self.send_value + self.delta:self.result = Trueself.recieved = Trueclass TestPlus(unittest.TestCase):def test_param(self):pb = PlusTester("Plus/in", "Plus/out", 2.1)self.assertTrue(pb.run())def test_default_param(self):pb = PlusTester("Plus2/in", "Plus2/out", 10.0)self.assertTrue(pb.run())def test_standalone(self):pb = PlusTester("Plus2/out", "Plus3/out", 2.5)self.assertTrue(pb.run())def test_remap(self):pb = PlusTester("plus_remap/in", "remapped_output", 2.1)self.assertTrue(pb.run())def test_chain(self):pb = PlusTester("Plus2/in", "Plus3/out", 12.5)self.assertTrue(pb.run())if __name__ == '__main__':rospy.init_node('plus_local') rostest.unitrun('test_nodelet', 'test_plus', TestPlus)
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.
rosunit.unitrun是对python 接口封装,创建XML的测试结果。
以下(3)和(4)是对unittest运行方式的,高级补充说明。
(3)Test suite使用
如果一次运行多个case进行测试,可用test suite
test/my_test_cases.py
import unittestclass CaseA(unittest.TestCase):def runTest(self):my_var = True# do some things to my_var which might change its value...self.assertTrue(my_var)class CaseB(unittest.TestCase):def runTest(self):my_var = True# do some things to my_var which might change its value...self.assertTrue(my_var)class MyTestSuite(unittest.TestSuite):def __init__(self):super(MyTestSuite, self).__init__()self.addTest(CaseA())self.addTest(CaseB())
以上代码把case A&&B,加入到test suite中,可以像运行指定的test case一样运行指定的suite
test/run_tests.py
# rosunit
rosunit.unitrun('test_package', 'test_name', 'test.my_test_cases.MyTestSuite')# rostest
rostest.rosrun('test_package', 'test_name', 'test.my_test_cases.MyTestSuite')
(4)通过名字加载tests
test/my_tests.py
class MyTestCase(unittest.TestCase):def test_a(self):self.assertTrue(True)def test_b(self):self.assertTrue(True)
运行test case(MyTestCase)的2个test
rosunit.unitrun('test_package', 'test_name', 'test.my_tests.MyTestCase.test_a')
rosunit.unitrun('test_package', 'test_name', 'test.my_tests.MyTestCase.test_b')
或者
from my_tests import MyTestCaserosunit.unitrun('test_package', 'test_name', MyTestCase)
rosunit.unitrun('test_package', 'test_name', 'test.my_tests.MyTestCase') //等效于上一句
2 编写调用test node启动文件
rostest调用test node的文件通常扩展名是.test,有时可能也会扩展名使用.launch,文件中<test>标签指定要运行的test node。
<test>标签与正常node属性基本一致(例外:没有属性 respawn,output;忽略machine属性),必须属性包括test-name,pkg,type,其它都是可选属性。
如:<test test-name="test_1_2" pkg="mypkg" type="test_1_2.py" time-limit="10.0" args="--test1 --test2" />
3 编写CMakeLists.txt文件
3.1 对于gtest
if(CATKIN_ENABLE_TESTING)find_package(rostest REQUIRED)add_rostest_gtest(tests_mynode test/mynode.test src/test/test_mynode.cpp [more cpp files])target_link_libraries(tests_mynode ${catkin_LIBRARIES})
endif()
add_rostest_gtest(target_name test_file source_file+)
3.2对于python unittest
if(CATKIN_ENABLE_TESTING)find_package(rostest REQUIRED)add_rostest(test/mytest.test)
endif()
也可以传递参数或者添加依赖
if(CATKIN_ENABLE_TESTING)find_package(rostest REQUIRED)add_rostest(test/my_first_test.test ARGS myarg1:=true myarg2:=false)catkin_download_test_data(${PROJECT_NAME}_saloon.baghttp://downloads.foo.com/bags/saloon.bagDESTINATION ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_SHARE_DESTINATION}/testMD5 01603ce158575da859b8afff5b676bf9)add_rostest(test/my_second_test.test DEPENDENCIES ${PROJECT_NAME}_saloon.bag)
endif()
4 运行测试
4.1 编译和运行一起自动进行
(1)自动运行全部测试
在build文件夹下运行命令make run_tests,完成二进制测试程序生成,同时会自动调用test目录下的所欲.test文件。
(2)运行单个测试
catkin_make run_tests<TAB><TAB>
(3)测试结果总结
catkin_test_results build/test_results
4.2 使用rostest命令手动运行
rostest -h
Usage: rostest [options] [package] <filename>
Options:
-h, --help show this help message and exit
-t, --text Run with stdout output instead of XML output
--pkgdir=PKG_DIR package dir
--package=PACKAGE package
--results-filename=RESULTS_FILENAME
results_filename
--results-base-dir=RESULTS_BASE_DIR
The base directory of the test results. The test
result file is created in a subfolder name PKG_DIR.
-r, --reuse-master Connect to an existing ROS master instead of spawning
a new ROS master on a custom port
-c, --clear Clear all parameters when connecting to an existing
ROS master (only works with --reuse-master)
如: rostest test_rospy rospy.test
===================以下rostest使用流程的一些辅助内容===================
5 使用现成的Test Nodes进行测试
5.1 hztest test node
rostest package自带test node,用于测试某个node的发布rate,如果topic匹配了specification则成功,默认测试的平均rate。
可用参数~check_intervals (bool, default: False)Check message-to-message interval, instead of just average overall publication rate.
<launch><node name="talker" pkg="test_ros" type="talker.py" /><param name="hztest1/topic" value="chatter" /> <param name="hztest1/hz" value="10.0" /><param name="hztest1/hzerror" value="0.5" /><param name="hztest1/test_duration" value="5.0" /> <test test-name="hztest_test" pkg="rostest" type="hztest" name="hztest1" />
</launch>
5.2 paramtest
测试注册到参数服务器的参数是否符合规范。
<!-- Test if a specific parameter is not empty. -->
<test pkg="rostest" type="paramtest" name="paramtest_nonempty"test-name="paramtest_nonempty"><param name="param_name_target" value="param_nonempty" /><param name="test_duration" value="5.0" /><param name="wait_time" value="20.0" />
</test><!-- Test if a specific parameter is empty. -->
<test pkg="rostest" type="paramtest" name="paramtest_empty"test-name="paramtest_empty"><param name="param_name_target" value="param_empty" /><param name="test_duration" value="5.0" /><param name="wait_time" value="30.0" />
</test><!-- Test if a specific parameter carries a specific value. -->
<test pkg="rostest" type="paramtest" name="paramtest_value_specific_correct"test-name="paramtest_value_specific_correct"><param name="param_name_target" value="param_value_specific" /><param name="param_value_expected" value="Opensource Robotics is forever." /><param name="test_duration" value="5.0" /><param name="wait_time" value="30.0" />
</test>
ROS-----rostest tool使用相关推荐
- ROS(1和2)机器人操作系统相关书籍、资料和学习路径
ROS机器人相关书籍与资料(更新日期2017年11月) ROS发展10年了,已经逐渐成为通用的机器人操作系统标准.ROS 2相关资料链接:http://blog.csdn.net/zhangrelay ...
- [ros robot] --- 机器人系统仿真
1 机器人系统仿真概念 机器人系统仿真:是通过计算机对实体机器人系统进行模拟的技术,在 ROS 中,仿真实现涉及的内容主要有三:对机器人建模(URDF).创建仿真环境(Gazebo)以及感知环境(Rv ...
- VIO测试准备——使用imu_utils和kalibr进行相机与IMU标定
0 前言 近期主要学习VIO.相机使用海康的两个支持外触发的单目相机,完成硬件同步外触发后,固联安装在机器人机体两侧,搭建了一个简易的双目相机.IMU使用的是LPMS-USBAL2(老型号,官方已经不 ...
- kalibr使用笔记
官网 GitHub - ethz-asl/kalibr: The Kalibr visual-inertial calibration toolboxThe Kalibr visual-inertia ...
- 二十九、URDF集成Rviz基本流程
文章目录 一.机器人系统仿真 1.1 相关组件 二.基本流程 2.1 需求 2.2 流程 一.机器人系统仿真 1.1 相关组件 URDF:Unified Robot Description Forma ...
- rviz的使用与显示
rviz的使用与显示 界面主要分为左侧设置区域,中间的显示区域和右侧的视角设置区域.最上方是与导航相关的工具. 什么是rviz: The ROS Visualization Tool ,即机器人操作系 ...
- Kalibr 之 Camera-IMU 标定 (总结)
Overview 欢迎访问 持续更新:https://cgabc.xyz/posts/db22c2e6/ ethz-asl/kalibr is a toolbox that solves the fo ...
- 关于传感器标定(imu标定,camera标定,camera-imu联合标定)
博主最近在帮同门做实验.关于传感器这些标定也是初次接触,使用orb-slam3代码包.其中涉及一些传感器标定,这里就把我用的东西汇总一下. 目录 1.imu标定 1.1 使用imu_utlies标定 ...
- ROS系列——ONVIF Device Test Tool测试工具获取网络摄像头的rtsp
ROS系列--ONVIF Device Test Tool测试工具获取网络摄像头的rtsp 1.说明 2.ONVIF Device Test Tool工具下载及安装 3.连接网络摄像头 4.获取视频测 ...
最新文章
- R语言ggplot2可视化:在可视化图像中添加对角线(diagonal line)
- Zend Framework数据库操作(1)
- 计算机处理信息的方式
- mysql proxy性能差_mysql性能的检查和优化方法
- Zookeeper下载
- 计算机图形软件---OpenGL简介
- JavaScript实现Fast Powering算法(附完整源码)
- 前端开发神器Sublime里如何设置JSlint
- C语言小项目(画机器猫)
- 西门子触摸屏中显示HTML,西门子触摸屏上传问题
- Kali linux 学习笔记(十三)主动信息收集——端口扫描(UDP扫描、TCP扫描) 2020.2.22
- 日夕如是寒暑不间,基于Python3+Tornado6+APScheduler/Celery打造并发异步动态定时任务轮询服务
- python如何设置窗口为活动窗口
- 【echarts高级用法】在地理坐标系中镶嵌柱状图,在加上时间轴让图动起来
- 最新版本webrtc源代码在windows上的编译方法
- 优秀课件笔记之计算机软件立法保护
- java md5 加密工具类_JavaMD5加密工具类
- 什么是SQL触发器?SQL触发器是什么意思?
- 中国剩余定理 (孙子定理) 的证明和代码
- Java对象内存空间大小计算
热门文章
- 简述计算机数控系统的工作原理,数控原理与系统复习
- stc12串口收发计算机,STC12C5A60S2 串口中断接收程序
- 【IM项目】框架分析与部署
- 升级ambari spark至spark3.0.2 bad substitution 和scala.MatchError: x.x (of class java.lang.String)错误解决
- 软件工程:结构化软件设计方法 VS 面向对象软件设计方法
- 怎么在matlab 中制表符,matlab中用fprintf怎么写入空格 、制表符,回车换行等符号?...
- 2022年App分发渠道整理
- Webtop Html5 桌面App开发 -- 整合人人网登陆
- opencv+c++写的小游戏,泡泡堂超级基础版
- C语言函数——复数四则运算