Тестирование сценария lua с помощью busted
Я пытаюсь протестировать наши скрипты Lua Freeswitch с помощью busted и сталкиваюсь с проблемой. Суть в том, что мне нужно иметь возможность шпионить за кодом, как показано ниже
local req_host = session:getVariable('sip_req_host')
session:setVariable('curl_timeout', 0)
Но я не могу понять, как построить объект, для которого я должен установить _G.session. Лучший / единственный хороший пример того, как использовать busted, я могу найти по адресу https://github.com/chris-allnutt/unit-tested-corona/blob/master/mocks/button.lua но, похоже, он использует тот же простой синтаксис для создания фиктивного объекта, что делает перевернутая документация.
local button = {
x = 0,
y = 0,
addEventListener = function() end
}
Я могу видеть, как это будет работать для простых функций, которым не нужно ничего возвращать, но мне нужно иметь возможность получать и устанавливать переменные в объекте сеанса, используя функции getVariable и setVariable. Мой простой фиктивный объект выглядит следующим образом:
Session = {}
Session.__index = Session
function Session.create(params)
local session = {}
setmetatable(session, Session)
session.params = params
return session
end
function Session:getVariable(key)
return self.params[key]
end
function Session:setVariable(key, val)
self.params[key] = val
end
function Session:execute(cmd, code)
end
и тест выглядит следующим образом
require "busted"
require("test_utils")
describe("Test voip lua script", function()
it('Test webrtc bad domain', function()
domain = 'rtc.baddomain.com';
session_params = {['sip_req_host'] = domain,
['sip_req_user'] = 'TEST-WebRTC-Client',
["sip_from_user"] = 'testwebrtc_p_12345',
['sip_call_id'] = 'test@call_id',
['sip_authorized'] = 'false'}
exec_str = 'sofia_contact TEST-WebRTC-Client@'..domain;
api_params = {[exec_str] = 'error/user_not_registered'}
_G.session = mock(Session.create(session_params), 'execute')
_G.api = API.create(api_params)
_G.freeswitch = Freeswitch.create()
dofile("tested_script.lua")
assert.spy(_G.session.execute).called_with("respond", "407")
end)
end)
Я заканчиваю со следующим исключением. /usr/local/share/lua/5.2/luassert/spy.lua:78: попытка проиндексировать значение функции
Это исключение выдается luassert, зависимостью переброшенной библиотеки, в следующем операторе if
77:local function called_with(state, arguments)
78: if rawget(state, "payload") and rawget(state, "payload").called_with then
79: return state.payload:called_with(arguments)
80: else
81: error("'called_with' must be chained after 'spy(aspy)'")
82: end
83:end
Я довольно новичок в lua, поэтому кажется вероятным, что я просто упускаю какую-то очевидную часть языка, но любая помощь или указатели будут с благодарностью.
3 ответа
Таким образом, ответ, который я нашел после другого дня отладки, состоял в том, что да, вам нужно использовать таблицу в качестве объекта, который вы называете макетом. Однако, поскольку lua - очень прощающий язык, когда речь идет о построении объектов с вызываемыми параметрами, это все равно заканчивается работой. Я построил оболочку вокруг объекта по причинам, не связанным с этим вопросом, но вы можете увидеть, что я в итоге заставил работать ниже.
function SessionConstructor.create(params)
local session_constructor = {}
setmetatable(session_constructor, SessionConstructor)
session_constructor.session = {}
session_constructor.session.params = params
session_constructor.session.getVariable = function(self,key)
return self.params[key]
end
session_constructor.session.setVariable = function(self, key, val)
self.params[key] = val
end
session_constructor.session.execute = function(self, cmd, code)
end
return session_constructor
end
function SessionConstructor:construct()
return self.session
end
Одно важное предостережение: из-за того, что вы должны передавать self в функции, которые будут вызываться с помощью синтаксиса lua ":", метод слежки за тем, с какими функциями вызывался, изменяется, как показано в тестовом файле ниже.
require "busted"
require "test_utils"
describe("Test voip lua script", function()
it('Test webrtc bad domain', function()
domain = 'rtc.baddomain.com';
session_params = {['sip_req_host'] = domain,
['sip_req_user'] = 'TEST-WebRTC-Client',
["sip_from_user"] = 'testwebrtc_p_12345',
['sip_call_id'] = 'test@call_id',
['sip_authorized'] = 'false'}
local sess_con = SessionConstructor.create(session_params)
exec_str = 'sofia_contact TEST-WebRTC-Client@'..domain;
local api_con = APIConstructor.create()
api_con:expect_exec(exec_str, 'error/user_not_registered')
_G.session = mock(sess_con:construct())
_G.api = mock(api_con:construct())
_G.freeswitch = create_freeswitch()
dofile("tested_script.lua")
assert.spy(session.execute).was.called_with(session, "respond", "407")
assert.spy(session.execute).was_not.called_with("respond", "407") --This is unfortunate
end)
end)
mod_lua во FreeSWITCH использует слегка настроенный интерпретатор Lua, и вы, похоже, используете другой интерпретатор Lua, который установлен на вашем хосте. Я думаю, они не будут легко работать вместе.
Я сделал некоторый реверс-инжиниринг busted
сценарий bin, и пришел к следующему сценарию (давайте назовем его runner.lua
):
busted = require 'busted.core'()
local environment = require 'busted.environment'(busted.context)
function unpack(t, i)
i = i or 1
if t[i] ~= nil then
return t[i], unpack(t, i + 1)
end
end
busted.getTrace = function(element, level, msg)
level = level or 3
local info = debug.getinfo(level, 'Sl')
info.traceback = debug.traceback('', level)
info.message = msg
if msg ~= nil then
freeswitch.consoleLog("NOTICE", msg)
end
local file = busted.getFile(element)
return file.getTrace(file.name, info)
end
busted.safe = function(descriptor, run, element, setenv)
if setenv and (type(run) == 'function' or getmetatable(run).__call) then
-- prioritize __call if it exists, like in files
environment.wrap(getmetatable(run).__call or run)
end
busted.context.push(element)
local trace, message
local ret = { xpcall(run, function(msg)
message = busted.rewriteMessage(element, msg)
freeswitch.consoleLog("ERR", message)
trace = busted.getTrace(element, 3, msg)
end) }
if not ret[1] then
busted.publish({ 'error', descriptor }, element, busted.context.parent(element), message, trace)
end
busted.context.pop()
return unpack(ret)
end
require 'busted.init'(busted)
local checkTag = function(name, tag, modifier)
local found = name:find('#' .. tag)
return (modifier == (found ~= nil))
end
local checkTags = function(name)
for i, tag in pairs(tags) do
if not checkTag(name, tag, true) then
return nil, false
end
end
for i, tag in pairs(excludeTags) do
if not checkTag(name, tag, false) then
return nil, false
end
end
return nil, true
end
local getTrace = function(filename, info)
local index = info.traceback:find('\n%s*%[C]')
info.traceback = info.traceback:sub(1, index)
return info, false
end
local file = setmetatable({
getTrace = getTrace
}, {
__call = loadfile("/path/scripts/main_spec.lua")
})
busted.executors.file("main_spec.lua", file)
local failures = 0
local errors = 0
busted.subscribe({ 'error' }, function()
errors = errors + 1
end)
busted.subscribe({ 'test', 'end' }, function(element, parent, status)
if status == 'failure' then
failures = failures + 1
end
end)
busted.publish({ 'suite', 'start' })
busted.execute()
busted.publish({ 'suite', 'end' })
freeswitch.consoleLog("NOTICE", "Failures: " .. failures)
freeswitch.consoleLog("NOTICE", "Errors: " .. errors)
Скрипт работает только с одним файлом, /path/scripts/main_spec.lua
вещь, но все еще пригодна для использования. Что вы можете сделать с этим runner.lua
скрипт, запускает его с luarun
с консоли Freeswitch:
fs_cli
luarun /path/to/runner.lua
И вы получите выход там.