ROS系统python代码测试之rostest
ROS系统中提供了测试框架,可以实现python/c++代码的单元测试,python和C++通过不同的方式实现,
之后的两篇文档分别详细介绍各自的实现步骤,以及测试结果和覆盖率的获取。
ROS系统中python代码测试介绍
关于测试代码的写法细节请参考官方wiki文档,http://wiki.ros.org/unittest,本文主要说明使用中的坑。
ROS中python代码的测试可以有两种实现方式一是节点级的集成测试,可以测试节点创建和消息收发的整个过程;二是代码级的单元测试,在测试用例中导入被测代码进行测试。
python代码测试中可能遇到的问题及优化修改
1、创建启动测试的roslaunch文件
rostest相关的roslaunch请参考 http://wiki.ros.org/roslaunch/XML/test
<launch>
<node name="nodename" pkg="pkgname" type="被测文件的py"/>
<test test-name="testnodename" pkg="pkgname" time-limit="500.0" type="测试文件py" args="--cov"/>
</launch>
关注点1:
time-limit这个标签限制了用例运行的最长时间,如果用例耗时超过这个时间,那么用例会自动以超时失败结束,默认值是60s。如果用例较多运行时间长的话,需要
设置合理的值;
关注点2:
args 这个标签可以向测试程序中传入参数,--cov的作用是测试完成后生成覆盖率文件,下面详细介绍。
2、测试结果文件获取
参见上一篇介绍环境变量了文章,通过ROS_TEST_RESULTS_DIR这个变量可以修改测试结果输出的位置。
3、覆盖率统计
我使用的ros版本是indigo,这个版本中rostest对覆盖率统计的代码需要进一步优化。
优化点1:rostest.rosrun(package_name, test_name, test_case_class, sysargs=None)
根据wiki中对rostest.rosrun的描述,该函数应该有第五个参数coverage_packages,该参数表示待测试package list.
优化后的函数rostest.rosrun(package_name, test_name, test_case_class, sysargs=None, coverage_packages=None)
优化点2:rostest覆盖率统计完善
覆盖率统计需要首先a安装python代码覆盖率工具coverge,参考http://coverage.readthedocs.org/en/latest/
修改rostest.rosrun代码,使代码能够输出xml_report,为什么要输出xml报告呢,因为The report is compatible with Cobertura reports.
这一点很关键,在jenkins持续集成环境中需要这一步骤的工作。jenkins中的Cobertura插件可以解析xml_report文件,然后将python代码的详细覆盖率信息显示在用例的测试结果中。
/opt/ros/indigo/lib/python2.7/dist-packages/rostest/__init__.py 中函数的修改
def rosrun(package, test_name, test, sysargs=None, coverage_packages=None):
"""
Run a rostest/unittest-based integration test. @param package: name of package that test is in
@type package: str
@param test_name: name of test that is being run
@type test_name: str
@param test: test class
@type test: unittest.TestCase
@param sysargs: command-line args. If not specified, this defaults to sys.argv. rostest
will look for the --text and --gtest_output parameters
@type sysargs: list
"""
if sysargs is None:
# lazy-init sys args
import sys
sysargs = sys.argv #parse sysargs
result_file = None
for arg in sysargs:
if arg.startswith(XML_OUTPUT_FLAG):
result_file = arg[len(XML_OUTPUT_FLAG):]
text_mode = '--text' in sysargs
coverage_mode = '--cov' in sysargs
if coverage_mode:
_start_coverage(coverage_packages) import unittest
import rospy coverresult = os.getenv('ROS_TEST_RESULTS_DIR') + '/coverage/' suite = unittest.TestLoader().loadTestsFromTestCase(test)
if text_mode:
result = unittest.TextTestRunner(verbosity=2).run(suite)
else:
result = rosunit.create_xml_runner(package, test_name, result_file).run(suite)
if coverage_mode:
_stop_coverage(coverage_packages, coverresult)
rosunit.print_unittest_summary(result) # shutdown any node resources in case test forgets to
rospy.signal_shutdown('test complete')
if not result.wasSuccessful():
import sys
sys.exit(1)
def _stop_coverage(packages, html=None):
"""
@param packages: list of packages to generate coverage reports for
@type packages: [str]
@param html: (optional) if not None, directory to generate html report to
@type html: str
"""
if _cov is None:
return
import sys, os
try:
_cov.stop()
# accumulate results
_cov.save() # - update our own .coverage-modules file list for
# coverage-html tool. The reason we read and rewrite instead
# of append is that this does a uniqueness check to keep the
# file from growing unbounded
if os.path.exists('.coverage-modules'):
with open('.coverage-modules','r') as f:
all_packages = set([x for x in f.read().split('\n') if x.strip()] + packages)
else:
all_packages = set(packages)
with open('.coverage-modules','w') as f:
f.write('\n'.join(all_packages)+'\n') try:
# list of all modules for html report
all_mods = [] # iterate over packages to generate per-package console reports
for package in packages:
pkg = __import__(package)
m = [v for v in sys.modules.values() if v and v.__name__.startswith(package)]
all_mods.extend(m) # generate overall report and per module analysis
_cov.report(m, show_missing=0)
for mod in m:
res = _cov.analysis(mod)
print("\n%s:\nMissing lines: %s"%(res[0], res[3])) if html: print("="*80+"\ngenerating html coverage report to %s\n"%html+"="*80)
_cov.html_report(all_mods, directory=html)
_cov.xml_report(all_mods, outfile=html + 'cover.xml')
except ImportError as e:
print("WARNING: cannot import '%s', will not generate coverage report"%package, file=sys.stderr)
except ImportError as e:
print("""WARNING: cannot import python-coverage, coverage tests will not run.
To install coverage, run 'easy_install coverage'""", file=sys.stderr)
/opt/ros/indigo/lib/python2.7/dist-packages/rosunit/pyunit.py 文件修改
def unitrun(package, test_name, test, sysargs=None, coverage_packages=None):
"""
Wrapper routine from running python unitttests with
JUnit-compatible XML output. This is meant for unittests that do
not not need a running ROS graph (i.e. offline tests only). This enables JUnit-compatible test reporting so that
test results can be reported to higher-level tools. WARNING: unitrun() will trigger a sys.exit() on test failure in
order to properly exit with an error code. This routine is meant
to be used as a main() routine, not as a library. @param package: name of ROS package that is running the test
@type package: str
@param coverage_packages: list of Python package to compute coverage results for. Defaults to package
@type coverage_packages: [str]
@param sysargs: (optional) alternate sys.argv
@type sysargs: [str]
"""
if sysargs is None:
# lazy-init sys args
import sys
sysargs = sys.argv import unittest if coverage_packages is None:
coverage_packages = [package] #parse sysargs
result_file = None
for arg in sysargs:
if arg.startswith(XML_OUTPUT_FLAG):
result_file = arg[len(XML_OUTPUT_FLAG):]
text_mode = '--text' in sysargs coverage_mode = '--cov' in sysargs or '--covhtml' in sysargs
if coverage_mode:
start_coverage(coverage_packages) # create and run unittest suite with our xmllrunner wrapper
suite = unittest.TestLoader().loadTestsFromTestCase(test)
if text_mode:
result = unittest.TextTestRunner(verbosity=2).run(suite)
else:
result = create_xml_runner(package, test_name, result_file).run(suite)
if coverage_mode:
#cov_html_dir = 'covhtml_test' if '--covhtml' in sysargs else None
cov_html_dir = os.getenv('ROS_TEST_RESULTS_DIR') + '/coverage/'
stop_coverage(coverage_packages, html=cov_html_dir) # test over, summarize results and exit appropriately
print_unittest_summary(result) if not result.wasSuccessful():
import sys
sys.exit(1)
def stop_coverage(packages, html=None):
"""
@param packages: list of packages to generate coverage reports for
@type packages: [str]
@param html: (optional) if not None, directory to generate html report to
@type html: str
"""
if _cov is None:
return
import sys, os
try:
_cov.stop()
# accumulate results
_cov.save() # - update our own .coverage-modules file list for
# coverage-html tool. The reason we read and rewrite instead
# of append is that this does a uniqueness check to keep the
# file from growing unbounded
if os.path.exists('.coverage-modules'):
with open('.coverage-modules','r') as f:
all_packages = set([x for x in f.read().split('\n') if x.strip()] + packages)
else:
all_packages = set(packages)
with open('.coverage-modules','w') as f:
f.write('\n'.join(all_packages)+'\n') try:
# list of all modules for html report
all_mods = [] # iterate over packages to generate per-package console reports
for package in packages:
pkg = __import__(package)
m = [v for v in sys.modules.values() if v and v.__name__.startswith(package)]
all_mods.extend(m) # generate overall report and per module analysis
_cov.report(m, show_missing=0)
for mod in m:
res = _cov.analysis(mod)
print("\n%s:\nMissing lines: %s"%(res[0], res[3])) if html: print("="*80+"\ngenerating html coverage report to %s\n"%html+"="*80)
_cov.html_report(all_mods, directory=html)
_cov.xml_report(all_mods, outfile=html + 'cover.xml')
except ImportError as e:
print("WARNING: cannot import '%s', will not generate coverage report"%package, file=sys.stderr)
except ImportError as e:
print("""WARNING: cannot import python-coverage, coverage tests will not run.
To install coverage, run 'easy_install coverage'""", file=sys.stderr)
4、测试代码例子
if __name__ == '__main__':
for arg in sys.argv:
print arg
testfiles = []
testfiles.append('被测试的python package')
testfiles.append('被测试的python package')
rostest.rosrun(PKG, NAME, 测试类名, sys.argv, testfiles)
ROS系统python代码测试之rostest的更多相关文章
- ROS系统C++代码测试之gtest
1. 安装gtestsudo apt-get install libgtest-dev 2.修改CMakeLists.txtfind_package(GTest REQUIRED)uncommend ...
- ros 使用python代码启动launch文件
在开发中我们经常会遇到使用python代码启动launch文件这样的问题.一般的做法是使用subprocess调用roslaunch.但是这种方法使用起来并不方便.要涉及到自己去控制进程的状态.由于r ...
- 光伏电池测控系统python代码
'''硬件keithley万用表和程控电源visa是VXIplug&play系统联盟制定的一套标准.python实现VISA,形成pyviva模块'''###IV测试系统的部分程序代码 fro ...
- 树莓派(Raspbian系统)中使用pyinstaller封装Python代码为可执行程序
一.前言 将做好的Python软件运行在树莓派上时,不想公开源码,就需要对文件进行封装(或称打包),本文主要介绍使用pyinstaller封装Python代码为可执行程序. Python是一个脚本语言 ...
- 基于深度学习的人脸性别识别系统(含UI界面,Python代码)
摘要:人脸性别识别是人脸识别领域的一个热门方向,本文详细介绍基于深度学习的人脸性别识别系统,在介绍算法原理的同时,给出Python的实现代码以及PyQt的UI界面.在界面中可以选择人脸图片.视频进行检 ...
- (二)ROS系统架构及概念 ROS Architecture and Concepts 以Kinetic为主更新 附课件PPT
第2章 ROS系统架构及概念 ROS Architecture and Concepts PPT说明: 正文用白色,命令或代码用黄色,右下角为对应中文译著页码. 这一章需要掌握ROS文件系统,运行图级 ...
- SLAM+语音机器人DIY系列:(二)ROS入门——2.ROS系统整体架构
摘要 ROS机器人操作系统在机器人应用领域很流行,依托代码开源和模块间协作等特性,给机器人开发者带来了很大的方便.我们的机器人“miiboo”中的大部分程序也采用ROS进行开发,所以本文就重点对ROS ...
- 【转载】ROS系统整体架构
目录 1.从文件系统级理解 2.从计算图级理解 3.从开源社区级理解 由于ROS系统的组织架构比较复杂,简单从一个方面来说明很难说清楚.按照ROS官方的说法,我们可以从3个方面来理解ROS系统整体架构 ...
- ROS-2 : ROS系统层级结构
一.ROS文件系统层级 ROS的文件和文件夹按如下层级来组织:
随机推荐
- 坑!坑!坑!防不胜防的unsigned int的运算
我很早之前就知道,unsigned int与int运算的时候,int会被转化为unsigned int来进行运算.一直觉得定这条规则的人是极度反人类的,虽说unsigned int可以表示更大的正值, ...
- 3、Javascript学习 - IT软件人员学习系列文章
接下来,我们开始进入Javascript语言的学习. Javascript语言是一种解释性的语言,不同于ASP.NET.C#语言的这种编译性的语言.它随着HTML网页的发布而发布,就是说嵌入到HTML ...
- H5一二事
先回顾一下WEB技术的几个阶段 Web 1.0 内容为主,主要流行HTML和CSS Web 2.0 动态网页,流行AJAX/JavaScript/DOM H5 时代,WEB开发回归富客户端 那么H5肯 ...
- 从刚刚「简书」平台的短暂异常,谈Nginx An error occurred报错~
09.26简书平台的短暂异常 An error occurred. Sorry, the page you are looking for is currently unavailable. Plea ...
- 十五天精通WCF——第六天 你必须要了解的3种通信模式
wcf已经说到第六天了,居然还没有说到这玩意有几种通信模式,惭愧惭愧,不过很简单啦,单向,请求-响应,双工模式,其中的第二种“请求-响应“ 模式,这个大家不用动脑子都清楚,这一篇我大概来分析下. 一: ...
- 用struts2获取session、request、parmeter的方法
package com.hanqi.action; import java.util.Map; import com.opensymphony.xwork2.ActionContext; public ...
- ORA-01858: 在要求输入数字处找到非数字字符
数据库 date 字段问题 insert into WK_RE_LE (DACL_FILE_ID,DACL_GROUP_ID,BDCDYH,DACL_LENGTH,ISVALID,DACL ...
- 奇 arch/i386/kernel/head.o(.text+0x3e): undefined reference to `stack_start'
当linux/linkage.h 是dos格式保存,即以\r\n作行结束,gcc-2.96/redhat-7.3报错
- JAVA插入sql代码
插入数据 import java.sql.*; /** * @version 2012-02-22 * @author */ public class InsertDemo { public stat ...
- java的访问权限
Java语言中有4中访问修饰符:friendly(默认).private.public和protected. public :能被所有的类(接口.成员)访问. protected:只能被本类.同一个包 ...