Тестирование сценария 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

И вы получите выход там.

Другие вопросы по тегам