背景介绍

配合CppUTest单元测试框架,lcov提供了一套比较完整的工程工具来对UT覆盖率进行度量。但对有些团队来说,历史负担太重,大量的遗留代码没有相应的UT。在这种情况下,对新增代码进行覆盖率检查,可能对团队来说是一种可行性较强的措施。在此目标基础上,并提出如下需求:

1)利用现有的lcov资源;

2)可以对指定git cmmit提交的代码进行UT覆盖率检查;

3)可以指定需要UT覆盖率检查的软件模块、文件;

4)可以设置UT覆盖率阈值;

5)检查结果可视化展示,有良好的用户体验;

为实现如上需求,开发了一个ut_incremental_check.py 工具。其在jenkins集成的效果截图如下:

图一:每次构建后生成新增代码UT覆盖率报告:Unittest - incremental code coverage report

图二:新增代码UT覆盖率报告详细信息

图三:点击具体的uncovered line行号可以直接“电梯”直达到本行代码位置进行查看

工具介绍

ut_incremental_check.py有4个参数:

<since>..<until>:指定git commit SHA范围

<monitor_c_files>:指定需要关注的文件或目录列表,此参数要符合json数据格式

<lcov_dir>:lcov生成的目标文件目录

<threshold>:对新增代码UT覆盖率的下限要求。取值范围在(0,1]范围。

总体的工作流程见如下help说明。

$ ./ut_incremental_check.py

PURPOSE:
calculate UT coverage of git commits' new code

USAGE:
./ut_incremental_check.py <since>..<until> <monitor_c_files> <lcov_dir> <threshold>
example:
./ut_incremental_check.py "227b032..79196ba" '["source/soda/sp/lssp/i2c-v2/ksource"]' "coverage" 0.6

WORK PROCESS:
get changed file list between <since> and <until> , filter by <monitor_c_files> options;
get changed lines per changed file;
based on <lcov_dir>, search .gcov.html per file, and get uncover lines;
create report file:ut_incremental_check_report.html and check <threshold> (cover lines/new lines).

UT:
./ut_incremental_check.py ut

jenkins配置介绍

jenkins job shell命令示例:

# 运行UT(CppUTest需要使能CPPUTEST_USE_GCOV配置,此处细节与本文无关,不展开讨论)
bash -ex bspmake ut# 生成UT覆盖率信息
lcov --capture --directory tmp/unittest/i2c-v2/ksource -b source/soda/sp/lssp/i2c-v2/unittest/ --output-file coverage.info# 生成UT覆盖率html报告
genhtml coverage.info -p $WORKSPACE --output-directory coverage# 生成增量代码UT覆盖率html报告
./ut_incremental_check.py $GIT_PREVIOUS_SUCCESSFUL_COMMIT".."$GIT_COMMIT '["source/soda/sp/lssp/i2c-v2/ksource"]' "coverage" 0.8# 返回结果
exit $?

jenkins HTML报告配置示例:  

附源码:

ut_incremental_check.py

#!/usr/bin/python
# -*- coding: utf-8 -*-
######################################################################
# Purpose:    calculate UT coverage of git commits' new code
# Useage:    ./ut_incremental_check.py
# Version:    Initial    Version    by wahaha02
######################################################################__version__ = 'V1.0'
__author__ = 'wahaha02'
__date__ = '2016-7-25'
__doc__ = '''
PURPOSE:
calculate UT coverage of git commits' new codeUSAGE:
./ut_incremental_check.py <since>..<until> <monitor_c_files> <lcov_dir> <threshold>
example:
./ut_incremental_check.py "227b032..79196ba" '["source/soda/sp/lssp/i2c-v2/ksource"]' "coverage" 0.6WORK PROCESS:
get changed file list between <since> and <until> , filter by <monitor_c_files> options;
get changed lines per changed file;
based on <lcov_dir>, search .gcov.html per file, and get uncover lines;
create report file:ut_incremental_check_report.html and check <threshold> (cover lines/new lines).UT:
./ut_incremental_check.py ut
'''__todo__ = '''
TODO LIST:1. support svn2. refactory html report by django web template3. add commit info in html report4. prompt user/commit/date info when mouse point to uncovered line5. ...
'''import sys, os, re
import json
import commands
from HTMLParser import HTMLParser
from pprint import *DEBUG = 0class GcovHTMLParser(HTMLParser):def __init__(self):HTMLParser.__init__(self)self.uncovers = []self.covers = []self.islineNum = Falseself.lineNum = 0def handle_starttag(self, tag, attrs):if tag == "span":for a in attrs:if a == ('class', 'lineNum'):self.islineNum = Trueif a == ('class', 'lineNoCov'):self.uncovers.append(self.lineNum)if a == ('class', 'lineCov'):self.covers.append(self.lineNum)def handle_data(self, data):if self.islineNum:try:self.lineNum = int(data)except:self.lineNum = -1def handle_endtag(self, tag):if tag == "span":self.islineNum = Falseclass UTCover(object) :def __init__(self, since_until, monitor, lcov_dir, thresh) :self.since, self.until = since_until.split('..')self.monitor = json.loads(monitor)self.lcov_dir = lcov_dirself.thresh = float(thresh)def get_src(self):# self.since, self.until, self.monitorsatus, output = commands.getstatusoutput("git diff --name-only %s %s" %(self.since, self.until))src_files = [f for f in output.split('\n') for m in self.monitor if m in f if os.path.splitext(f)[1][1:] in ['c', 'cpp']]if DEBUG: pprint(src_files)return src_filesdef get_change(self, src_files):# self.since, self.untilchanges = {}for f in src_files:satus, output = commands.getstatusoutput("git log --oneline %s..%s %s | awk '{print $1}'" %(self.since, self.until, f))commits = output.split('\n')cmd = "git blame %s | grep -E '(%s)' | awk  -F' *|)' '{print $6}'" %(f, '|'.join(commits))satus, lines = commands.getstatusoutput(cmd)changes[f] = [ int(i) for i in lines.split('\n') if i.isdigit() ]if DEBUG: pprint(changes)return changesdef get_ghp(self, f):gcovfile = os.path.join(self.lcov_dir, f + '.gcov.html')if not os.path.exists(gcovfile):return Noneghp = GcovHTMLParser()ghp.feed(open(gcovfile, 'r').read())return ghpdef get_lcov_data(self, changes):# self.lcov_diruncovers = {}lcov_changes = {}for f, lines in changes.items():ghp = self.get_ghp(f)if not ghp:uncovers[f] = lineslcov_changes[f] = linescontinueif DEBUG: print f, ghp.uncovers, ghp.covers, lineslcov_changes[f] = sorted(list(set(ghp.uncovers + ghp.covers) & set(lines)))uncov_lines = list(set(ghp.uncovers) & set(lines))if len(uncov_lines) != 0:uncovers[f] = sorted(uncov_lines)ghp.close()    return lcov_changes, uncoversdef create_uncover_trs(self, uncovers):tr_format = '''<tr><td class="coverFile"><a href="%(file)s.gcov.html">%(file)s</a></td><td class="coverFile">%(uncov_lines)s </td></tr>'''trs = ''for f,v in uncovers.items():gcovfile = os.path.join(self.lcov_dir, f + '.gcov.html')if os.path.exists(gcovfile):s = ''p = re.compile(r'^<span class="lineNum">\s*(?P<num>\d+)\s*</span>')for line in open(gcovfile, 'r').readlines():ps = p.search(line)if ps:s += '<a name="%s">' %ps.group('num') + line + '</a>'else:s += lineopen(gcovfile, 'w').write(s)    data = {'file':f, 'uncov_lines':", ".join(['<a href="%s.gcov.html#%d">%d</a>' %(f, i, i) for i in v])}trs += tr_format %datareturn trsdef create_report(self, changes, uncovers):change_linenum, uncov_linenum = 0, 0for k,v in changes.items():change_linenum += len(v)for k,v in uncovers.items():uncov_linenum += len(v)cov_linenum = change_linenum - uncov_linenumcoverage = round(cov_linenum * 1.0 / change_linenum if change_linenum > 0 else 1, 4)template = open('ut_incremental_coverage_report.template', 'r').read()data = {    'cov_lines':cov_linenum,'change_linenum':change_linenum,'coverage': coverage * 100,'uncover_trs': self.create_uncover_trs(uncovers)}open(os.path.join(self.lcov_dir, 'ut_incremental_coverage_report.html'),'w').write(template %data)    return coveragedef check(self):# main functionsrc_files = self.get_src()changes = self.get_change(src_files)lcov_changes, uncovers = self.get_lcov_data(changes)return 0 if self.create_report(lcov_changes, uncovers) > self.thresh else 1if len(sys.argv) == 1:print __doc__sys.exit(0)
if sys.argv[1] == 'ut':monitor, lcov_dir, threshold = ['["source/soda/sp/lssp/i2c-v2/ksource"]', "coverage", 0.8]test1 = ["b2016fdb..11440652", monitor, lcov_dir, threshold]if DEBUG: print "test1: ", test1ut = UTCover(*test1)src_files = ut.get_src()assert(src_files == [])changes = ut.get_change(src_files)assert(changes == {})lcov_changes, uncovers = ut.get_lcov_data(changes)assert(uncovers == {})rate = ut.create_report(changes, uncovers)assert(rate == 1)assert(ut.check() == 0)test2 = ["227b03259b33360e2309274f3927c38457d84dd3..79196baabed99661bd31a201ead6764f23a2884c", monitor, lcov_dir, threshold]if DEBUG: print "test2: ", test2ut = UTCover(*test2)src_files = ut.get_src()assert(src_files == ['source/soda/sp/lssp/i2c-v2/ksource/bsp_i2c_dev.c', 'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_cfcuctrl.c', 'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_opt.c', 'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_pcie.c'])changes = ut.get_change(src_files)assert(changes ==  {'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_pcie.c': [78], 'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_cfcuctrl.c': [56, 57, 58, 59, 60, 130, 131, 132], 'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_opt.c': [68, 69, 115, 118, 124, 125, 126, 454, 459, 460, 461, 462, 463, 464, 465, 466, 467, 468, 471, 721], 'source/soda/sp/lssp/i2c-v2/ksource/bsp_i2c_dev.c': [494, 496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 625, 626, 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652]})lcov_changes, uncovers = ut.get_lcov_data(changes)assert( lcov_changes == {'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_pcie.c': [78], 'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_cfcuctrl.c': [56, 57, 58, 59, 60, 130, 131, 132], 'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_opt.c': [125, 459, 461, 462, 471], 'source/soda/sp/lssp/i2c-v2/ksource/bsp_i2c_dev.c': [496, 498, 502, 503, 504, 625, 629, 630, 631, 633, 634, 636, 638, 639, 643, 644, 649, 650]}) assert(uncovers == {'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_pcie.c': [78], 'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_cfcuctrl.c': [56, 57, 58, 59, 60, 130, 131, 132], 'source/soda/sp/lssp/i2c-v2/ksource/chips/bsp_i2c_opt.c': [125, 471], 'source/soda/sp/lssp/i2c-v2/ksource/bsp_i2c_dev.c': [502, 503, 504, 643, 644]})rate = ut.create_report(changes, uncovers)assert(0.8 > rate > 0.6)assert(ut.check() == 1)test3 = ['d98b93e705a227389e7cdc4b43252f4194a6cb7a..e8876ff5fe8ee0e61865315a67bd395f5d7f63f7 ',monitor, lcov_dir, threshold]if DEBUG: print "test3: ", test3ut = UTCover(*test3)assert(ut.check() == 0)    sys.exit(0)ret = UTCover(*sys.argv[1:]).check()
sys.exit(ret)

ut_incremental_coverage_report.template

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>coverage report</title><link rel="stylesheet" type="text/css" href="gcov.css">
</head><body><table width="100%%" border=0 cellspacing=0 cellpadding=0><tr><td class="title">Unittest - incremental code coverage report</td></tr><tr><td class="ruler"><img src="glass.png" width=3 height=6 alt=""></td></tr><tr><td width="100%%"><table cellpadding=1 border=0 width="100%%"><tr><td></td><td width="33%%" class="headerCovTableHead">UT covered</td><td width="33%%" class="headerCovTableHead">Total</td><td width="33%%" class="headerCovTableHead">Coverage</td></tr><tr><td class="headerItem">Incremental Lines:</td><td class="headerCovTableEntry">%(cov_lines)s</td><td class="headerCovTableEntry">%(change_linenum)s</td><td class="headerCovTableEntry">%(coverage)s %%</td></tr><tr><td><img src="glass.png" width=3 height=3 alt=""></td></tr></table></td></tr><tr><td class="ruler"><img src="glass.png" width=3 height=3 alt=""></td></tr></table><center><br><table width="100%%" cellpadding=1 cellspacing=1 border=0><tr><td width="60%%" class="tableHead">File </td><td width="40%%" class="tableHead">Uncovered Lines </td></tr>%(uncover_trs)s</table></center><br></body>
</html>

View Code

--EOF--

转载于:https://www.cnblogs.com/wahaha02/p/5733755.html

基于lcov实现的增量代码UT覆盖率检查相关推荐

  1. iOS 覆盖率检测原理与增量代码测试覆盖率工具实现

    背景 对苹果开发者而言,由于平台审核周期较长,客户端代码导致的线上问题影响时间往往比较久.如果在开发.测试阶段能够提前暴露问题,就有助于避免线上事故的发生.代码覆盖率检测正是帮助开发.测试同学提前发现 ...

  2. ios 单元测试覆盖率怎么查看_iOS 覆盖率检测原理与增量代码测试覆盖率工具实现...

    背景 对苹果开发者而言,由于平台审核周期较长,客户端代码导致的线上问题影响时间往往比较久.如果在开发.测试阶段能够提前暴露问题,就有助于避免线上事故的发生.代码覆盖率检测正是帮助开发.测试同学提前发现 ...

  3. Android 增量代码测试覆盖率工具实践

    当业务快速发展,新业务不断出现,开发同学粗心的情况下,难免会出现少测漏测的情况,如何保证新增代码有足够的测试覆盖率?当一段正常的代码,开发却修改了,测试人员没有测试其功能,如果保证能够发现? 所以代码 ...

  4. Android增量代码测试覆盖率工具

    美团业务快速发展,新项目新业务不断出现,在项目开发和测试人员不足.开发同学粗心的情况下,难免会出现少测漏测的情况,如何保证新增代码有足够的测试覆盖率是我们需要思考的问题. 先看一个bug: 以上代码可 ...

  5. 多环境多需求并行下的代码测试覆盖率统计工具实现

    个人博客导航页(点击右侧链接即可打开个人博客):大牛带你入门技术栈 测试覆盖率常被用来衡量测试的充分性和完整性,也是测试有效性的一个度量.「敏捷开发」的大潮之下,如何在快速迭代的同时保证对被测代码的覆 ...

  6. Node.js 单元测试:我要写测试 - Mocha - Nodejs开源项目里怎么样写测试、CI和代码测试覆盖率

    -------------------------------------- 单元测试Express/NodeJs 个人理解, 1,如果不是测试http请求的单元测试,用Mocha, Chai等基本够 ...

  7. 基于自适应算法和增量式PID算法的模拟直升飞机控制系统

    基于自适应算法和增量式PID算法的模拟直升飞机控制系统 文章目录 基于自适应算法和增量式PID算法的模拟直升飞机控制系统 控制系统硬件 单片机系统 传感器系统介绍 直升机模拟系统介绍 系统模块介绍 A ...

  8. 基于git hooks的前端代码质量控制解决方案

    基于git hooks的前端代码质量控制解决方案 原文:https://github.com/kuitos/kuitos.github.io/issues/28 国际惯例先说下故事背景 通常情况下,如 ...

  9. 连连看+php,java基于swing实现的连连看代码

    本文实例讲述了java基于swing实现连连看代码.分享给大家供大家参考. 主要功能代码如下:package llkan; import javax.swing.*; import java.awt. ...

最新文章

  1. tar、gzip、gunzip、bzip2、zip、unzip
  2. WebIDE discovery when destination is selected from dropdown list
  3. c语言中判断输入是否为数字_C 判断
  4. 计算机对农业领域带来的变革,智慧农业将给农业带来哪些变化?
  5. pyecharts运行了但是没有图_新版pyecharts,Python可视化so easy and powerful !
  6. 后处理没有pui文件怎么打开_UG NX10.0四轴后处理下载(带PUI文件)
  7. 详解volatile关键字
  8. 用于Elasticsearch数据可视化和分析的强大工具
  9. c语言甘勇第二版第五章答案,C语言(1) - Patata的个人页面 - OSCHINA - 中文开源技术交流社区...
  10. Tcl Tutorial 笔记8 · proc
  11. iOS开发之权限判断(这里只说相册的权限判断status一直是PHAuthorizationStatusNotDetermined问题)
  12. java十次方项目链接 视频+资料+讲义
  13. 百度云网盘高速通道加速破解版 去广告纯净版干净云
  14. QQ界面的MSN聊天软件
  15. android sim卡状态改变广播,android监听SIM状态
  16. 干货!mysql初始密码
  17. 关于sigmoid函数几点介绍
  18. 极客日报:HarmonyOS 2.0用户数升至5000万;腾讯起诉抖音侵权《王者荣耀》,获赔60万?抖音:没侵权,已上诉!
  19. 计算机启动时 需换电池bells,Win7笔记本电脑提示请考虑更换电池怎么处理?
  20. 2022大学生免费(24元)申请个人软著专利(微信小程序)

热门文章

  1. 硬件项目开发 datasheet篇
  2. 给别人的比别人期待的多一些
  3. jquery兄弟标签_jQuery----获取兄弟元素的方法
  4. 产品周报第25期|博文分享新增「分享人」标识,「读者分享」可记录分享人带来的阅读量明细……
  5. 手机版c语言编译器能画图,大佬们,小菜鸟想问一问用vc编译器做简易画图软件...
  6. mysql求班级男女比例_男女比例失调的班级是什么样的?
  7. Latex 绘制流程图
  8. 蓝桥杯 蚂蚁感冒 不要模拟!
  9. Vue进阶(四十五):精解 ES6 Promise 用法
  10. vue的router部署从零开始