【问题标题】:How can I use reflection (at runtime) in Lua?如何在 Lua 中使用反射(在运行时)?
【发布时间】:2019-09-12 04:58:43
【问题描述】:

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

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

本地 _M = {}

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

...

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

【问题讨论】:

    标签: lua lua-5.1


    【解决方案1】:

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

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

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

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

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

    【讨论】:

      【解决方案2】:

      我今天早上实际上能够从一位同事那里找到答案。正如 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_logger 将全局替换依赖关系 path.to.module.loggermy_logger 假设它遵守相同的合同。

      【讨论】:

        【解决方案3】:

        我会为记录器编写模拟并将其设置在与原始记录器相同的路径下,但在不同的根目录下。然后为测试在 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 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
        

        【讨论】:

          猜你喜欢
          • 2012-01-13
          • 2018-10-12
          • 2012-08-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-22
          相关资源
          最近更新 更多