• 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>
    View Code

    关注点1

    time-limit这个标签限制了用例运行的最长时间,如果用例耗时超过这个时间,那么用例会自动以超时失败结束,默认值是60s。如果用例较多运行时间长的话,需要

    设置合理的值;

    关注点2

    args 这个标签可以向测试程序中传入参数,--cov的作用是测试完成后生成覆盖率文件,下面详细介绍。

    2、测试结果文件获取

    参见上一篇介绍环境变量了文章,通过ROS_TEST_RESULTS_DIR这个变量可以修改测试结果输出的位置。

    3、覆盖率统计

    我使用的ros版本是indigo,这个版本中rostest对覆盖率统计的代码需要进一步优化。

    优化点1rostest.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('
    ') if x.strip()] + packages)
            else:
                all_packages = set(packages)
            with open('.coverage-modules','w') as f:
                f.write('
    '.join(all_packages)+'
    ')
                
            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("
    %s:
    Missing lines: %s"%(res[0], res[3]))
    
                if html:
                    
                    print("="*80+"
    generating html coverage report to %s
    "%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('
    ') if x.strip()] + packages)
            else:
                all_packages = set(packages)
            with open('.coverage-modules','w') as f:
                f.write('
    '.join(all_packages)+'
    ')
                
            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("
    %s:
    Missing lines: %s"%(res[0], res[3]))
                        
                if html:
                    
                    print("="*80+"
    generating html coverage report to %s
    "%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)
  • 相关阅读:
    2018.11.15 RF antenna impedance-matching
    2018.11.14 Chopin’s
    2018.11.13 N4010A 通信设置
    2018.11.12 RF debug
    2018.11 企业战略课堂笔记4 -内部条件分析
    2018.11 企业战略课堂笔记3 五力模型
    2018.11 企业战略课堂笔记2 SWOT-4C战略
    2018.11 企业战略课堂笔记1 概论
    PyQt(Python+Qt)学习随笔:QTabWidget选项卡部件操作控制类属性movable和tabsClosable介绍
    PyQt(Python+Qt)学习随笔:QTabWidget选项卡部件外观展示类属性elideMode、documentMode、tabBarAutoHide、tabShape介绍
  • 原文地址:https://www.cnblogs.com/objectDetect/p/5049285.html
Copyright © 2020-2023  润新知