Testing and Debugging
Prerequisites: Error handling, Modules and require
Testing with busted
busted is the most popular Lua testing framework:
-- illustrative
-- spec/pricing_spec.lua
local pricing = require("pricing")
describe("pricing.discount", function()
it("gives no discount under 100", function()
assert.are.equal(0, pricing.discount(50))
end)
it("gives 10% at 100 or more", function()
assert.are.equal(20, pricing.discount(200))
end)
it("rejects negative totals", function()
assert.has_error(function() pricing.discount(-1) end, "total cannot be negative")
end)
end)
$ busted
+++
3 successes / 0 failures / 0 errors / 0 pending : 0.004 seconds
describe groups tests, it defines one, and assert.are.same compares
tables deeply. Use luassert spies and stubs to replace functions
during tests.
A tiny test runner
Where you can't install busted — inside a game or another host — a few lines of plain Lua are enough:
local passed, failed = 0, 0
local function test(name, fn)
local ok, err = pcall(fn)
if ok then passed = passed + 1
else failed = failed + 1; print("FAIL " .. name .. ": " .. tostring(err)) end
end
local function eq(a, b) if a ~= b then error(tostring(a) .. " ~= " .. tostring(b), 2) end end
local function discount(total)
if total < 0 then error("negative total") end
return total >= 100 and total * 0.1 or 0
end
test("no discount under 100", function() eq(discount(50), 0) end)
test("10% at 100", function() eq(discount(200), 20.0) end)
test("deliberately wrong", function() eq(discount(100), 5) end)
test("negative raises", function() assert(not pcall(discount, -1)) end)
print(passed .. " passed, " .. failed .. " failed")
FAIL deliberately wrong: main.lua:16: 10.0 ~= 5
3 passed, 1 failed
The debug library
local function whereAmI()
local info = debug.getinfo(2, "Sln")
return string.format("%s:%d in %s", info.short_src, info.currentline, info.name or "?")
end
local function process()
print(whereAmI())
end
process()
local function inspectLocals()
local a, b = 1, "two"
local i = 1
while true do
local name, value = debug.getlocal(1, i)
if not name then break end
if name ~= "i" then print(name, value) end
i = i + 1
end
end
inspectLocals()
main.lua:7 in process
a 1
b two
debug.getinfo describes a function or stack level; debug.getlocal
reads local variables; debug.traceback builds a stack trace;
debug.sethook runs code on every call, line, or N instructions — the
basis of debuggers and profilers.
Debuggers
- VS Code — the "Lua Debug" extension (by actboy168) supports Lua 5.1–5.4 and LuaJIT, with breakpoints and variable inspection.
- ZeroBrane Studio — built-in debugger, including remote debugging of LÖVE and other hosts.
- In Neovim, nvim-dap with
one-small-step-for-vimkinddebugs Neovim Lua itself.