如何在 Lua 中使用反射(在运行时)?
How can I use reflection (at runtime) in Lua?
我正在尝试在 Lua 中使用 Busted 对代码进行单元测试(我没有写,也不允许我此时出于业务原因重构),并且这个模块中没有概念类 也不是依赖注入。所以,我想用我制作的模拟记录器替换文件顶部所需的一些模块,即 local log = require("path.to.module.logger"):new()
来跟踪方法被调用的次数,即 logger:trace()
这样的与 Java 中 Mockito 中的 times()
一样。在 Java 中,我可以使用 Reflection.Utils 来达到这个目的。 Lua 中有什么等价物可以帮助使这个不可测试的代码可测试?
我已经尝试创建一个具有相同变量名称的全局变量 log
并使用此示例将其设置为等于我的模拟:https://www.lua.org/pil/14.2.html
本地 _M = {}
本地日志=要求("path.to.module.logger"):new()
...
函数_M.init(...)
log:trace("debug") # 我希望这个日志实例不是上面那个,而是我在运行时注入模块的那个
结束
"Reflection" 在 Lua 中并不是真正的东西,在 Java 这个词的意义上不是。作为一门使用Duck Typing的语言,一切都很开放。 Lua只有一种数据结构:atable。 Lua 中的所有内容 来自 table。模块只是 table return 由 require
.
加载的块编辑
table后面的内容和数据结构可以通过metatable隐藏起来,可以用来阻止正常的迭代过程(pairs
,ipairs
,等等)用于访问 table 中的元素。但是,您始终可以使用 getmetatable
提取元 table 本身并调用它;你甚至可以突破用 debug.getmetatable
.
隐藏 metatable 的常用方法
话虽这么说,因为 Lua 依赖于 Duck Typing,并且因为 Lua 的 API 非常开放,所以很难 全面 关于包装每个函数和 table。
例如,假设您要包装一个模块。这很容易;只需创建一个空的 table,它有一个 metatable,其元方法调用包装的模块方法。这适用于模块的直接 APIs。
但是,如果其中一个 API return 是一个 自身 需要包装的对象,会发生什么?您将如何区分专门的 API 对象和普通的 table 对象?同样重要的是,如果您能够成功识别出哪些 return 值需要包装,您将如何做到这一点?毕竟,如果他们将你的包装器 table 之一传递给包装器 API 函数,它现在需要解包 table 以便它可以将包装的 table 传递给被包装的实际功能。
今天早上我实际上能够从一位同事那里找到答案。 "Reflection" 并不是真的可能,正如尼可波拉斯在他的回答中建议的那样,然而,从 lua 文档 (http://lua-users.org/wiki/ModulesTutorial) 中,我们了解到:
Lua caches modules in the package.loaded table.
这意味着我们可以在失败的测试中覆盖 package.loaded
table 并且基本上在运行时替换紧耦合代码中的依赖项(很像通过 [=25 中的依赖注入在 Mockito 中进行模拟) =]).例如:
package.loaded["path.to.module.logger"] = my_logger
将在全球范围内用 my_logger
替换依赖项 path.to.module.logger
假设它遵守相同的合同。
我会为记录器编写模拟并将其设置在与原始记录器相同的路径下,但在不同的根目录下。然后为测试在 LUA_PATH
的开头添加带有模拟的文件夹
示例:
/tmp/a/package/logger.lua :
local _M = {}
_M.log = function()
print "Original logger"
end
return _M
/tmp/b/package/logger.lua:
local _M = {}
_M.log = function()
print "Mocked logger"
end
return _M
和测试/tmp/test/logger_spec.lua:
describe("Test suite", function()
it("Testing the mock", function ()
local log = require("package.logger")
log.log()
end)
end)
如果您将 LUA_PATH 设置为使用原始 :
export LUA_PATH="/tmp/a/?.lua;;"
并拨打电话:
busted logger_spec.lua
Original logger
●
1 success / 0 failures / 0 errors / 0 pending : 0.000527 seconds
现在将 LUA_PATH 指向您的模拟:
export LUA_PATH="/tmp/b/?.lua;;"
电话又被打断了
busted logger_spec.lua
Mocked logger
●
1 success / 0 failures / 0 errors / 0 pending : 0.000519 seconds
我正在尝试在 Lua 中使用 Busted 对代码进行单元测试(我没有写,也不允许我此时出于业务原因重构),并且这个模块中没有概念类 也不是依赖注入。所以,我想用我制作的模拟记录器替换文件顶部所需的一些模块,即 local log = require("path.to.module.logger"):new()
来跟踪方法被调用的次数,即 logger:trace()
这样的与 Java 中 Mockito 中的 times()
一样。在 Java 中,我可以使用 Reflection.Utils 来达到这个目的。 Lua 中有什么等价物可以帮助使这个不可测试的代码可测试?
我已经尝试创建一个具有相同变量名称的全局变量 log
并使用此示例将其设置为等于我的模拟:https://www.lua.org/pil/14.2.html
本地 _M = {}
本地日志=要求("path.to.module.logger"):new()
...
函数_M.init(...) log:trace("debug") # 我希望这个日志实例不是上面那个,而是我在运行时注入模块的那个 结束
"Reflection" 在 Lua 中并不是真正的东西,在 Java 这个词的意义上不是。作为一门使用Duck Typing的语言,一切都很开放。 Lua只有一种数据结构:atable。 Lua 中的所有内容 来自 table。模块只是 table return 由 require
.
table后面的内容和数据结构可以通过metatable隐藏起来,可以用来阻止正常的迭代过程(pairs
,ipairs
,等等)用于访问 table 中的元素。但是,您始终可以使用 getmetatable
提取元 table 本身并调用它;你甚至可以突破用 debug.getmetatable
.
话虽这么说,因为 Lua 依赖于 Duck Typing,并且因为 Lua 的 API 非常开放,所以很难 全面 关于包装每个函数和 table。
例如,假设您要包装一个模块。这很容易;只需创建一个空的 table,它有一个 metatable,其元方法调用包装的模块方法。这适用于模块的直接 APIs。
但是,如果其中一个 API return 是一个 自身 需要包装的对象,会发生什么?您将如何区分专门的 API 对象和普通的 table 对象?同样重要的是,如果您能够成功识别出哪些 return 值需要包装,您将如何做到这一点?毕竟,如果他们将你的包装器 table 之一传递给包装器 API 函数,它现在需要解包 table 以便它可以将包装的 table 传递给被包装的实际功能。
今天早上我实际上能够从一位同事那里找到答案。 "Reflection" 并不是真的可能,正如尼可波拉斯在他的回答中建议的那样,然而,从 lua 文档 (http://lua-users.org/wiki/ModulesTutorial) 中,我们了解到:
Lua caches modules in the package.loaded table.
这意味着我们可以在失败的测试中覆盖 package.loaded
table 并且基本上在运行时替换紧耦合代码中的依赖项(很像通过 [=25 中的依赖注入在 Mockito 中进行模拟) =]).例如:
package.loaded["path.to.module.logger"] = my_logger
将在全球范围内用 my_logger
替换依赖项 path.to.module.logger
假设它遵守相同的合同。
我会为记录器编写模拟并将其设置在与原始记录器相同的路径下,但在不同的根目录下。然后为测试在 LUA_PATH
的开头添加带有模拟的文件夹示例: /tmp/a/package/logger.lua :
local _M = {}
_M.log = function()
print "Original logger"
end
return _M
/tmp/b/package/logger.lua:
local _M = {}
_M.log = function()
print "Mocked logger"
end
return _M
和测试/tmp/test/logger_spec.lua:
describe("Test suite", function()
it("Testing the mock", function ()
local log = require("package.logger")
log.log()
end)
end)
如果您将 LUA_PATH 设置为使用原始 :
export LUA_PATH="/tmp/a/?.lua;;"
并拨打电话:
busted logger_spec.lua
Original logger
●
1 success / 0 failures / 0 errors / 0 pending : 0.000527 seconds
现在将 LUA_PATH 指向您的模拟:
export LUA_PATH="/tmp/b/?.lua;;"
电话又被打断了
busted logger_spec.lua
Mocked logger
●
1 success / 0 failures / 0 errors / 0 pending : 0.000519 seconds