废话在前

什么是代码覆盖率

来自百度百科

开发人员为何关注?

在我们的开发过程中,经常要用各种方式进行自测,或是各种 xUnit 系列,或是 postman,或是直接curl,在我们的代码交给 QA 同学之前,我们有必要知道自己的自测验过了多少内容,在这种情况,代码覆盖率就是一个重要的衡量指标。

openresty 中的代码覆率解决方案

我们如果想得到每一次执行的代码覆率,需要搞定两件事情:

  1. 可以外在的记录每一行的代码
  2. 在记录的同时,可以知道这一行的代码上下文是什么
  3. 更重要的是,我们需要尽可能的不动现有业务代码

对于第一点,lua的debug库中有一个非常神奇的钩子函数sethook,其官方文档如下:

其中文翻译大体如下:

在这个官方的说明里,lua已经贴心的告诉我们使用方式————配合debug.getinfo,那么debug.getinfo是什么?其实我们在之前讨论错误输出时已经使用过这个方法,其官方文档如下:

这个API的说明中文含义大体如下:

OK,有了这两个方法,我们的思路就变得很清析了:

  1. 在生命周期开始时注册钩子函数.
  2. 将每一次调用情况记录汇总.

这里有一个新的问题,就是,我们的汇总是按调用累加还是只针对每一次调用计算,本着实用的立场,我们是需要进行累加的,那么,需要使用ngx.share_dict 来保存汇总信息.

基于以上考虑,封装一个libs/test/hook.lua文件,内容如下:

local debug = load "debug"
local cjson = load "cjson"
local M = {}
local mt = { __index = M }
local sharekey = 'test_hook'
local cachekey = 'test_hook'
function M:new()
local ins = {}
local share = ngx.shared[sharekey]
local info ,ret = share:get(cachekey)
if info then
info = cjson.decode(info)
else
info = {}
end
ins.info = info
setmetatable(ins,mt)
return ins
end
function M:sethook ()
debug.sethook(function(event,line)
local info = debug.getinfo(2)
local s = info.short_src
local f = info.name
local startline = info.linedefined
local endline = info.lastlinedefined
if string.find(s,"lualib") ~= nil then
return
end
if self.info[s] == nil then
self.info[s]={}
end
if f == nil then
return
end
if self.info[s][f] ==nil then
self.info[s][f]={
start = startline,
endline=endline,
exec = {},
activelines = debug.getinfo(2,'L').activelines
}
end
self.info[s][f].exec[tostring(line)]=true end,'l')
end
function M:save()
local share = ngx.shared[sharekey]
local ret = share:set(cachekey,cjson.encode(self.info),120000)
end
function M:delete()
local share = ngx.shared[sharekey]
local ret = share:delete(cachekey)
self.info = {}
end
function M:get_report()
local res = {}
for f,v in pairs(self.info) do
item = {
file=f,
funcs={}
}
for m ,i in pairs(v) do
local cover = 0
local index = 0
for c,code in pairs(i.activelines) do
if i.activelines[c] then
index = index + 1
end
if i.exec[tostring(c)] or i.exec[c] then
cover = cover +1
end
end
item.funcs[#item.funcs+1] = { name = m ,coverage= string.format("%.2f",cover / index*100 ) .."%"}
end
res[#res+1]=item
end
return res
end
return M

这样,我们只需要在content_by_lua的最开始加上:

local hook = load "libs.test.hook"
local test = hook:new()
test:sethook()
--other code ..

在最末加上:

test:save()

即可统计代码覆盖率。

是的,没错,我们至今只增加了4行业务代码

但是统计了,应该怎么进行输出呢?

加个接口好了:)

因为现在lor用的多,所以,干脆加个lor的路由文件(libs/test/lorapi.lua):

local hook = require 'libs.test.hook'
local router = lor:Router ()
local M = {}
router:get('/test/coverage/json-report',
function(req,res,next)
local t = hook:new()
res:json(t:get_report())
end)
router:get('/test/coverage/txt-report',
function(req,res,next)
local t = hook:new()
local msg = "Report"
local rpt = t:get_report()
for i ,v in pairs(rpt) do
msg =msg.."\r\n"..v.file
for j,f in pairs(v.funcs) do
msg = msg .."\r\n\t function name:" .. f.name .."\tcoverage:"..f.coverage
end
end
msg =msg .."\r\nEnd"
res:send(msg)
end)
return router

这样,在我们的lor路由文件里加个requre,加个use,两行改动,而且是增加!!就达到我们的需求,检查代码的覆盖率.

05-07 15:08
查看更多