项目中用到了lua的单元测试框架busted,根据需求需要对busted源码进行分析,这次主要分析一下outputHandlers模块。
项目的地址见:点击打开链接;
默认busted的输出见runner.lua:如下两行代码。
options = tablex.update(require 'busted.options',options or {})
options.output = options.output or (isatty and 'utfTerminal' or 'plainTerminal')
options是命令行读进来的一些参数。如果命令行参数为空,options.output 则会返回(isatty and 'utfTerminal' or 'plainTerminal')
isatty简单来说就是检测给定的文件描述符是否链接到一个终端,因为lua中的io库中io.stdin、io.stdout以及io.stderr是默认提供的预定义的句柄,所以isatty为true,那么最终的options.output就会取utfTerminal或者plainTerminal。
tablex.updata可以在 /usr/local/share/lua/5.3/pl/tablex.lua中看到,其实就是表拷贝的过程,tablex.lua的位置可以root 用find 命令查找;function tablex.update (t1,t2)
assert_arg_writeable(1,t1)
assert_arg_iterable(2,t2)
for k,v in pairs(t2) do
t1[k] = v
end
return t1
end
当然,如果加了 -o /busted/outputHandlers/xxx.lua,那么在output_handler_loader.lua中会调用:
handler = require('busted.outputHandlers.' .. output)
这样最终会按照读取的格式进行显示输出。
outputHandlers目录下最重要的文件就是base.lua,其他就是不同输出格式的lua文件,包括gtest.lua、json.lua、junit.lua、plainTerminal.lua、sounds.lua、TAP.lua以及utfTerminal.lua。虽然格式文件多,但是查看代码,都包括以下三部分内容:
1.busted和handler的引入
local busted = require("busted")
local handler = require("busted.outputHandler.base")()
handler.testStart = function(element, parent)
-----xxxxxxx
end
handler.testEnd = function(element, parent, status, trace)
--xxxxxxxxxxxxxx
end
busted.subscribe({'test', 'start'}, handler.testStart)
busted.subscribe({'test', 'end'}, handler.testEnd)
这样就可以完成定制格式的输出;
其实outputHandlers模块有策略模式的影子,base.lua可以看成是strategy基类,gtest.lua等可以看成是不同的策略实现,”继承”于base.lua。base.lua中需要根据格式进行重写的方法包括:
busted.subscribe({ 'suite', 'reset' }, handler.baseSuiteReset, { priority = 1 })
busted.subscribe({ 'suite', 'start' }, handler.baseSuiteStart, { priority = 1 })
busted.subscribe({ 'suite', 'end' }, handler.baseSuiteEnd, { priority = 1 })
busted.subscribe({ 'test', 'start' }, handler.baseTestStart, { priority = 1, predicate = handler.cancelOnPending })
busted.subscribe({ 'test', 'end' }, handler.baseTestEnd, { priority = 1, predicate = handler.cancelOnPending })
busted.subscribe({ 'pending' }, handler.basePending, { priority = 1, predicate = handler.cancelOnPending })
busted.subscribe({ 'failure', 'it' }, handler.baseTestFailure, { priority = 1 })
busted.subscribe({ 'error', 'it' }, handler.baseTestError, { priority = 1 })
busted.subscribe({ 'failure' }, handler.baseError, { priority = 1 })
busted.subscribe({ 'error' }, handler.baseError, { priority = 1 })
function busted.subscribe(...)
return mediator:subscribe(...)
end
继续跟进在term/mediator.lua中可以看到mediator:subscribe的代码,其实就是在callback中添加回调函数(这块函数后续将继续分析)
subscribe = function(self, channelNamespace, fn, options)
return self:getChannel(channelNamespace):addSubscriber(fn, options)
end,
addSubscriber = function(self, fn, options)
local callback = Subscriber(fn, options)
local priority = (#self.callbacks + 1)
options = options or {}
if options.priority and
options.priority >= 0 and
options.priority < priority
then
priority = options.priority
end
table.insert(self.callbacks, priority, callback)
return callback
end,
handler.cancelOnPending
handler.subscribe
handler.getFullName
handler.format
handler.getDuration
handler.baseSuiteStart
handler.baseSuiteReset
handler.baseSuiteEnd
handler.baseTestStart
handler.baseTestEnd
handler.basePending
handler.baseTestFailure
handler.baseTestError
handler.baseError
local formatted = {
trace = debug or element.trace,
element = copyElement(element),
name = handler.getFullName(element),
message = message,
randomseed = parent and parent.randomseed,
isError = isError
}
这张表会保存每个element的信息,包括状态、调试信息、名称等;然后根据测试结果的不同将formatted表insert到不同的表中,最后再做不同的处理。
table.insert(handler.failures, handler.format(element, parent, message, debug))
table.insert(handler.errors, handler.format(element, parent, message, debug, true))
table.insert(insertTable, formatted)