bluebird75 / luaunit

LuaUnit is a popular unit-testing framework for Lua, with an interface typical of xUnit libraries (Python unittest, Junit, NUnit, ...). It supports several output formats (Text, TAP, JUnit, ...) to be used directly or work with Continuous Integration platforms (Jenkins, Maven, ...).
Other
565 stars 136 forks source link

testing without globals #124

Closed tyiss closed 3 years ago

tyiss commented 4 years ago

Hi

I'm using luaunit to unit test lua code in openresty environment. since openresty version 1.15.8.1, there is a new warn message that indicate use of globals.

[lua] _G write guard:12: __newindex(): writing a global lua variable ('Testing') which may lead to race conditions between concurrent requests, so prefer the use of 'local' variables

is there an option to pass the tests as an argument to the run function, so that i can set the tests something like the following:


local luaunit = require('luaunit')

local test_module = {}

function test_module.test_first()
    local a="toto"
    local b="titi"
    luaunit.assertEquals( a, b ) --oops, two values are not equal
end

luaunit.LuaUnit.run("--name", "test", "--output", "junit", "--tests", test_module)

or any other idea ?

mm-pagely commented 4 years ago

@tyiss the write guard in openresty is implemented via a metatable on the global namespace table (_G), so a possible workaround for this is to drop the metatable in your tests:

-- remove the global metatable
setmetatable(_G, nil)

local luaunit = require('luaunit')

function TestMyModule()
    --- ...
end

I do this for the unit test scripts in a project of mine in addition to linting the source code with luacheck to catch any unwanted global variable usage.

Even so, I'd also prefer to namespace tests into a table (instead of using globals) if possible.

bluebird75 commented 4 years ago

Sorry for the delay.

There is an internal function in LuaUnit which fits your bill : runSuiteByInstances() . It expects a list of (name, table reference) where table is a test table containing test functions.

Your example would become :

 luaunit.LuaUnit.run( { { "test_module", test_module }, {"test_module2", test_module2} } )

One issue when passing explicitely the test table containing all test functions is that LuaUnit can not know its name. That's why you have to provide the name as well and is slightly less friendly. I should consider accepting a whole test module where I would pickup the test table themselves.

If you also want to control the output type in your program, you have to explicitly instantiate the runner :

local runner = luaunit.LuaUnit.new()
runner:setOutputType("junit")
luaunit.LuaUnit.fname = "test"
os.exit( runner.runSuiteByInstances( { { "test_module", test_module }, {"test_module2", test_module2} } ) )

When writing the example, I realize that there is no simple way to pass the junit filename, hence the set of explicit variable on LuaUnit. I will fix this.

Tell me if it improves the situation.

bluebird75 commented 4 years ago

Starting from revision ba66dd8081be3becb06a0100aa0f16e68cd09702 (just updated 5 minutes ago) you can simplify the running to this:


local runner = luaunit.LuaUnit.new()
runner:setOutputType("junit", "test")
os.exit( runner.runSuiteByInstances( { { "test_module", test_module }, {"test_module2", test_module2} } ) ) ```
tyiss commented 4 years ago

@mm-pagely thanks for the tip. i'm using it, since some libs exposes globals.

@bluebird75 thanks. it works good. it worth to mention that you're example contains a typo. it works when i use runner:runSuiteByInstances (using self implicitly)


local runner = luaunit.LuaUnit.new()
runner:setOutputType("junit", "test")
os.exit( runner:runSuiteByInstances( { { "test_module", test_module }, {"test_module2", test_module2} } ) ) ```