如何在 Lua 没有 OO 的情况下进行单元测试?

在 Lua 中,可以编写简单的模块,例如:

local database = require 'database'
local M = {}

function M:GetData()
    return database:GetData()
end

return M

当被需要时,会加载一次,并且所有的副本都会加载相同的副本。

如果想采用面向对象的方法,可以做如下处理:

local M = {}
M.__index = M

function M:GetData()
    return self.database:GetData()
end

return function(database)
    local newM = setmetatable({}, M)
    newM.database = database
    return newM
end

在这里,M 仅被加载一次,每个新的副本只是拥有自己的数据并使用原始 M 的方法。

在测试时,采用面向对象的方法可以传入一个假的 'database' 并检查其是否被调用,但是采用第一种方法无法实现。

因此,我的问题是如何使第一种方法支持 DI/testing,而不使其具有类似的特性?

我的想法是将其包装在一个闭包中,如下所示:

local mClosure = function(database)
    local M = {}

    function M:GetData()
        return database:GetData()
    end

    return M
end

return mClosure

但是每次调用它时,它都会创建一个新的 M 副本,因此将失去前两种方法的优势。

点赞
用户4984564
用户4984564

那显然是需要使用 Lua debug 库 的一个用例。借助该库,只需修改函数的 upvalue 并注入依赖项。此外,还可以考虑使用 require。只需调用一次所需的数据库模块,创建收集数据的小表,然后重定向到原始模块并将其放入 package.loaded 中。这样,下一次调用 require 时,它将返回模块的修改版本。面向对象的方法是在像 Ruby 这样的语言中执行此类操作的一种方式,但在 Lua 中,我们有更好的方法来访问模块或函数,而无需专门设计用于此目的。

local real_db = require 'db'
local fake_db = setmetatable({}, {__index=db})
function fake_db.exec(query) print('running query: '..query) end -- dummy function
function fake_db.something(...) print('doing something'); real_db.something(...) end
package.loaded.db = fake_db
require 'my_tests' -- this in turn requires 'db', but gets the fake one
package.loaded.db = real_db
-- After this point, `require 'db'` will return the original module
2018-05-31 08:24:10