基于lcov实现的增量代码UT覆盖率检查
背景介绍
配合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 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
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 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
''' __todo__ = '''
TODO LIST:
1. support svn
2. refactory html report by django web template
3. add commit info in html report
4. prompt user/commit/date info when mouse point to uncovered line
5. ...
''' import sys, os, re
import json
import commands
from HTMLParser import HTMLParser
from pprint import * DEBUG = 0 class GcovHTMLParser(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.uncovers = []
self.covers = []
self.islineNum = False
self.lineNum = 0 def handle_starttag(self, tag, attrs):
if tag == "span":
for a in attrs:
if a == ('class', 'lineNum'):
self.islineNum = True
if 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 = -1 def handle_endtag(self, tag):
if tag == "span":
self.islineNum = False class 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_dir
self.thresh = float(thresh) def get_src(self):
# self.since, self.until, self.monitor
satus, 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_files def get_change(self, src_files):
# self.since, self.until
changes = {}
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 changes def get_ghp(self, f):
gcovfile = os.path.join(self.lcov_dir, f + '.gcov.html')
if not os.path.exists(gcovfile):
return None ghp = GcovHTMLParser()
ghp.feed(open(gcovfile, 'r').read()) return ghp def get_lcov_data(self, changes):
# self.lcov_dir
uncovers = {}
lcov_changes = {} for f, lines in changes.items():
ghp = self.get_ghp(f)
if not ghp:
uncovers[f] = lines
lcov_changes[f] = lines
continue if DEBUG: print f, ghp.uncovers, ghp.covers, lines
lcov_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, uncovers def 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 += line
open(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 %data return trs def create_report(self, changes, uncovers):
change_linenum, uncov_linenum = 0, 0
for k,v in changes.items():
change_linenum += len(v)
for k,v in uncovers.items():
uncov_linenum += len(v) cov_linenum = change_linenum - uncov_linenum
coverage = 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 coverage def check(self):
# main function
src_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 1 if 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: ", test1
ut = 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: ", test2
ut = 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: ", test3
ut = 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>
--EOF--
基于lcov实现的增量代码UT覆盖率检查的更多相关文章
- iOS 覆盖率检测原理与增量代码测试覆盖率工具实现
		背景 对苹果开发者而言,由于平台审核周期较长,客户端代码导致的线上问题影响时间往往比较久.如果在开发.测试阶段能够提前暴露问题,就有助于避免线上事故的发生.代码覆盖率检测正是帮助开发.测试同学提前发现 ... 
- Nodejs开源项目里怎么样写测试、CI和代码测试覆盖率
		测试 目前主流的就bdd和tdd,自己查一下差异 推荐 mocha和tape 另外Jasmine也挺有名,angularjs用它,不过挺麻烦的,还有一个选择是qunit,最初是为jquery测试写的, ... 
- 使用Jacoco获取 Java 程序的代码执行覆盖率
		Jacoco是Java Code Coverage的缩写,顾名思义,它是获取Java代码执行覆盖率的一个工具,通常用它来获取单元测试覆盖率.它通过分析Java字节码来得到代码执行覆盖率,因此它还可以分 ... 
- Web端PHP代码函数覆盖率测试解决方案
		1. 关于代码覆盖率 衡量代码覆盖率有很多种层次,比如行覆盖率,函数/方法覆盖率,类覆盖率,分支覆盖率等等.代码覆盖率也是衡量测试质量的一个重要标准,对于黑盒测试来说,如果你不确定自己的测试用例是否真 ... 
- 基于jQuery实现滚动新闻代码下载
		分享一款基于jQuery实现滚动新闻代码下载.这是一款基于bootstrup 3实现的响应式jQuery滚动新闻插件.效果图如下: 在线预览 源码下载 实现的代码. html代码: <div ... 
- jacoco-1-java代码测试覆盖率之本地环境初体验
		前言 jacoco是一个开源的覆盖率工具,它针对的开发语言是java,其使用方法很灵活,可以插桩到Ant.Maven中,可以使用其JavaAgent技术监控Java程序等. 那么本次主要使用对java ... 
- 【Lua】实现代码执行覆盖率统计工具
		一.如何评估测试过程的测试情况? 很多时候完成功能测试后就会发布上线,甚至交叉和回归都没有足够的时间去执行,然后通过线上的补丁对遗漏的问题进行修复.如果可以在发布前了解本次测试过程所覆盖代码执行的比例 ... 
- jQuery基于ajax实现星星评论代码
		本文实例讲述了jQuery基于ajax实现星星评论代码.分享给大家供大家参考.具体如下: 这里使用jquery模仿点评网的星星评论功能,Ajax评论模块,鼠标点击星星即可评价,下边是分数,可以点击后给 ... 
- 基于eclipse的mybatis映射代码自动生成的插件
		基于eclipse的mybatis映射代码自动生成的插件 分类: JAVA 数据库 工具相关2012-04-29 00:15 2157人阅读 评论(9) 收藏 举报 eclipsegeneratori ... 
随机推荐
- angularjs 中的setTimeout(),setInterval() / $interval 和 $timeout
			$interval window.setInterval的Angular包装形式.Fn是每次延迟时间后被执行的函数. 间隔函数的返回值是一个承诺.这个承诺将在每个间隔刻度被通知,并且到达规定迭代次数后 ... 
- Spring MVC返回Map格式JSON数据
			问题描述: ajax中走error : function(e) {} 问题背景: 在测试controller层时,试过了ResponseEntity<ResponseModel>这种类型返 ... 
- scrapy加载cookies登陆
			import scrapy from xxxx.items import XXXXItem from scrapy.http.request import Request class ZndsSpid ... 
- apache如何解决跨域资源访问
			很多时候,大中型网站为了静态资源分布式部署,加快访问速度,减轻主站压力,会把静态资源(例如字体文件.图片等)放在独立服务器或者CDN上,并且使用独立的资源域名(例如res.test.com) 但是在实 ... 
- 设计模式--享元模式Flyweight(结构型)
			一.享元模式 在一个系统中如果有多个相同的对象,这些对象有部分状态是可以共享的,我们运用共享技术就能有效地支持大量细粒度的对象. 二.例子 举个围棋的例子,围棋的棋盘共有361格,即可放361个棋子. ... 
- appium 滑动
			前些日子,配置好了appium测试环境,至于环境怎么搭建,参考:http://www.cnblogs.com/tobecrazy/p/4562199.html 知乎Android客户端登陆:htt ... 
- python leetcode 1
			开始刷 leetcode, 简单笔记下自己的答案, 目标十一结束之前搞定所有题目. 提高一个要求, 所有的答案执行效率必须要超过 90% 的 python 答题者. 1. Two Sum. class ... 
- C# Stream
			转载:C# 温故而知新:Stream篇(一.二) http://www.cnblogs.com/JimmyZheng/archive/2012/03/17/2402814.html#no2 http: ... 
- ATM
			package duzhaonan;import java.util.Scanner;import javax.swing.JOptionPane;class Account{//创建的账户类 Str ... 
- WordPress一键部署网站
			每个人心里都有一个建站梦,所以今天作为我第一篇文章,就给大家圆了这场梦. 今天我来详细的一步一步带领大家利用WordPress程序来建立自己的小站以及解决直接域名访问(本地安装wordpress请阅读 ... 
