背景介绍

配合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 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覆盖率检查的更多相关文章

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

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

  2. Nodejs开源项目里怎么样写测试、CI和代码测试覆盖率

    测试 目前主流的就bdd和tdd,自己查一下差异 推荐 mocha和tape 另外Jasmine也挺有名,angularjs用它,不过挺麻烦的,还有一个选择是qunit,最初是为jquery测试写的, ...

  3. 使用Jacoco获取 Java 程序的代码执行覆盖率

    Jacoco是Java Code Coverage的缩写,顾名思义,它是获取Java代码执行覆盖率的一个工具,通常用它来获取单元测试覆盖率.它通过分析Java字节码来得到代码执行覆盖率,因此它还可以分 ...

  4. Web端PHP代码函数覆盖率测试解决方案

    1. 关于代码覆盖率 衡量代码覆盖率有很多种层次,比如行覆盖率,函数/方法覆盖率,类覆盖率,分支覆盖率等等.代码覆盖率也是衡量测试质量的一个重要标准,对于黑盒测试来说,如果你不确定自己的测试用例是否真 ...

  5. 基于jQuery实现滚动新闻代码下载

    分享一款基于jQuery实现滚动新闻代码下载.这是一款基于bootstrup 3实现的响应式jQuery滚动新闻插件.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div ...

  6. jacoco-1-java代码测试覆盖率之本地环境初体验

    前言 jacoco是一个开源的覆盖率工具,它针对的开发语言是java,其使用方法很灵活,可以插桩到Ant.Maven中,可以使用其JavaAgent技术监控Java程序等. 那么本次主要使用对java ...

  7. 【Lua】实现代码执行覆盖率统计工具

    一.如何评估测试过程的测试情况? 很多时候完成功能测试后就会发布上线,甚至交叉和回归都没有足够的时间去执行,然后通过线上的补丁对遗漏的问题进行修复.如果可以在发布前了解本次测试过程所覆盖代码执行的比例 ...

  8. jQuery基于ajax实现星星评论代码

    本文实例讲述了jQuery基于ajax实现星星评论代码.分享给大家供大家参考.具体如下: 这里使用jquery模仿点评网的星星评论功能,Ajax评论模块,鼠标点击星星即可评价,下边是分数,可以点击后给 ...

  9. 基于eclipse的mybatis映射代码自动生成的插件

    基于eclipse的mybatis映射代码自动生成的插件 分类: JAVA 数据库 工具相关2012-04-29 00:15 2157人阅读 评论(9) 收藏 举报 eclipsegeneratori ...

随机推荐

  1. js中bind,call,apply方法的应用

    最近用js的类写东西,发现一个无比蛋疼的事,那就是封装的类方法中的this指针经常会改变指向,失去上下文,导致程序错误或崩溃. 比如: function Obj(){ this.type = &quo ...

  2. Webview和Html

    参考http://blog.csdn.net/chenzheng_java/article/details/6260872 <html><header> <title&g ...

  3. No.25

    每天三件事必做: 1.背单词: 2.跑步: 3.读书.

  4. python --enable-shared

    https://github.com/docker-library/python/issues/21 例如编译安装python3.5.2,脚本如下: wget https://s3.cn-north- ...

  5. Go简介

    Go是Google开发的一种编译型,並發型,并具有垃圾回收功能的编程语言. 罗伯特·格瑞史莫(Robert Griesemer),罗勃·派克(Rob Pike)及肯·汤普逊于2007年9月开始设计Go ...

  6. visual studio installer 打包123

    下载安装visual studio installer

  7. sql查询删除重复数据

    数据库UserInfo 删除重复数据 即删除重复的用户名手机号 同一个用户名手机号只保留一个用户 01.根据多个字段查询重复数据 with data1 as( select MobilePhone,N ...

  8. jquery中on绑定事件

    之前项目中动态创建的标签元素  在绑定事件的时候  都是无效  无论如何都不能触发 eg:在页面加载完成之后   再由脚本动态创建的<div>元素  在绑定事件的时候 例如click事件 ...

  9. 响应者链条,如何获取最佳的点击view 以及内部实现

    事件的产生与传递 事件是如何产生与传递的? 当发生触摸事件后,系统会将该事件加入到一个由UIApplication管理的事件队列中. UIApplication会从时间队列中取出最前面的时间,并将事件 ...

  10. RBAC基于角色的访问控制

    RBAC(Role-Based Access Control,基于角色的访问控制),就是用户通过角色与权限进行关联.简单地说,一个用户拥有若干角色,每一个角色拥有若干权限.这样,就构造成"用 ...