1

我正在尝试在 Lua 中使用 Busted(我没有编写,也不允许我此时出于业务原因重构)对代码进行单元测试,并且在这个类模块中没有任何概念,也没有依赖注入。因此,我想替换文件顶部所需的一些模块,即local log = require("path.to.module.logger"):new()用我制作的用于跟踪方法调用次数的模拟记录器,logger:trace()例如times()Java 中的Mockito 中的with。在 Java 中,我可以为此目的使用 Reflection.Utils。Lua 中的什么等价物可以帮助使这个不可测试的代码可测试?

我已经尝试log使用此示例创建一个具有相同变量名的全局变量并将其设置为等于我的模拟:https ://www.lua.org/pil/14.2.html

本地 _M = {}

本地日志 = require("path.to.module.logger"):new()

...

function _M.init(...) log:trace("debug") # 我希望这个日志实例不是上面的那个,而是我在运行时注入到模块中的那个

4

3 回答 3

1

“反射”在 Lua 中并不是一个真正的东西,在这个术语的 Java 意义上也不是。作为使用Duck Typing的语言,一切都非常开放。Lua 只有一种数据结构:一张表。Lua 中的一切都来自表。模块只是由require.

表背后的内容和数据结构可以通过元表隐藏,这可用于防止访问表中元素的正常迭代过程(pairsipairs等)。但是,您始终可以使用getmetatable来提取元表本身并调用它;你甚至可以突破隐藏元表的常用方法debug.getmetatable

话虽如此,因为 Lua 依赖于 Duck Typing,并且因为 Lua 的 API 非常开放,所以很难全面地包装每个函数和表。

例如,假设您要包装一个模块。这很容易;只需创建一个空表,其中包含一个元表,其元方法调用包装的模块方法。这适用于模块的直接 API。

但是,如果其中一个 API 返回一个本身需要包装的对象,会发生什么?您将如何区分专用 API 对象和普通表?同样重要的是,如果您可以成功确定需要包装哪些返回值,您将如何做到这一点?毕竟,如果他们将您的包装器表之一传递给包装器 API 函数,它现在需要解开该表,以便它可以将包装器表传递给被包装的实际函数。

于 2019-04-23T04:08:26.740 回答
0

今天早上我实际上能够从一位同事那里找到答案。正如 Nicol Bolas 在他的回答中所建议的那样,“反射”实际上是不可能的,但是,从 lua 文档(http://lua-users.org/wiki/ModulesTutorial)中,我们了解到:

Lua caches modules in the package.loaded table.

这意味着我们可以在破坏的测试中覆盖package.loaded表,并且本质上在运行时替换紧密耦合代码中的依赖关系(很像通过 Java 中的依赖注入在 Mockito 中进行模拟)。例如:

package.loaded["path.to.module.logger"] = my_loggerpath.to.module.logger将在全局范围内替换依赖项,并my_logger假设它遵守相同的合同。

于 2019-04-23T13:37:48.553 回答
0

我会为记录器编写我的模拟并将其设置在与原始记录器相同的路径下,但在不同的目录根目录下。然后为测试在 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 设置为使用 original : export LUA_PATH="/tmp/a/?.lua;;" 并调用 busted :

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
于 2019-04-23T13:43:13.440 回答