lunarmodules / busted

Elegant Lua unit testing.
https://lunarmodules.github.io/busted/
MIT License
1.42k stars 186 forks source link

How could I test a local function #605

Closed kakascx closed 5 years ago

kakascx commented 5 years ago

When I attempted to make a unit test for a local function, the following is my file and unit test file

-- test.lua
local function FirstToUpper(str)
  return (str:gsub("^%l", string.upper))
end
--test_spec.lua
 describe("The first character shoule be",function()
    it("uppercase",function()
    local str=firstToUpper("steven")
    assert.are.same(str,"Steven")
    end)
  end)

Then it reports the error just like,

 attempt to call global 'FirstToUpper' (a nil value)

This is just an example, I found that it's impossible to test a local function and it always reports the same error just like I mentioned before. Is there any limitation of busted to test the local function? Or there is other ways to make a unit test of a local funtion

Tieske commented 5 years ago

Typical pattern is to use a global variable as a flag:

-- module.lua
local M = {} -- module table

local function FirstToUpper(str)
  return (str:gsub("^%l", string.upper))
end

function M:exported_function(param1, param2)
  -- does something special
end

if _TEST then
  -- Note: we prefix it with an underscore, such that the test function and real function have
  -- different names. Otherwise an accidental call in the code to `M.FirstToUpper` would
  -- succeed in tests, but later fail unexpectedly in production
  M._FirstToUpper = FirstToUpper
end

return M

-- test_spec.lua
_G._TEST = true
local myModule = require "module"

describe("The first character shoule be",function()
    it("uppercase",function()
    local str=myModule._firstToUpper("steven")
    assert.are.same(str,"Steven")
    assert.are.same("Steven",str)  -- better because "expected" is always the first argument
    end)
end)

hth

kakascx commented 5 years ago

Typical pattern is to use a global variable as a flag:

-- module.lua
local M = {} -- module table

local function FirstToUpper(str)
  return (str:gsub("^%l", string.upper))
end

function M:exported_function(param1, param2)
  -- does something special
end

if _TEST then
  -- Note: we prefix it with an underscore, such that the test function and real function have
  -- different names. Otherwise an accidental call in the code to `M.FirstToUpper` would
  -- succeed in tests, but later fail unexpectedly in production
  M._FirstToUpper = FirstToUpper
end

return M

-- test_spec.lua
_G._TEST = true
local myModule = require "module"

describe("The first character shoule be",function()
    it("uppercase",function()
  local str=myModule._firstToUpper("steven")
  assert.are.same(str,"Steven")
  assert.are.same("Steven",str)  -- better because "expected" is always the first argument
    end)
end)

hth

I have tried in the way you mentioned and it works. Could I consider this way as the conditional compilation in C code? And I wonder if it will make an influence of performance.

ildar commented 5 years ago

Typical pattern is to use a global variable as a flag:

Thanks a lot for this hint. I asked myself this question recently (last week to be precise). It looks this is the "best practice" kind of hint. Isn't it worth be added to FAQ?

Tieske commented 5 years ago

I have tried in the way you mentioned and it works. Could I consider this way as the conditional compilation in C code? And I wonder if it will make an influence of performance.

It's in the nature of dynamic languages. There is no (or negligible) performance impact, because only when the module is loaded for the first time there is 1 conditional check that exports the function. After that there is no impact anymore.

Tieske commented 5 years ago

it is documented, see http://olivinelabs.com/busted/#private

Tieske commented 5 years ago

please close

ildar commented 5 years ago

True. Yet

We believe the correct way to address this is to refactor your code to make it more externally testable.

Is there an example or a book explaining this somewhere? I want to learn!

Tieske commented 5 years ago

bikeshedding on testing. In the past busted would automatically set the _TEST global. Because of the mentioned quote, that was removed by the authors.

Theoretically right, code should be externally testable. But sometimes a lot of complexity lives in private code, and in those cases testing it externally can be really hard. Hence, personally, I think its perfectly fine to test private functionality like this.

ildar commented 5 years ago

Thanks for clarification! IMHO BDD lacks an experience sharing hub or does anyone knows one? I'll go ask on #lua then ))

kakascx commented 5 years ago

@Tieske Thx a million, I focus on Kong recently and develop a plugin, that's why I use busted. But I found that it's not work when I invoke APIs of openresty & kong. I am afraid I have to refactor my code. I will close this issue and continue to find the best solution

Tieske commented 5 years ago

@kakascx please open an issue on Kong Nation (and tag me if you like)