Lua I/O 依赖注入

我是一个Lua新手。我正在使用LunityLeMock对Lua 5.1代码进行单元测试。

我的类是StorageManager。我正在对其load()方法进行单元测试,该方法从磁盘加载文件。我不希望我的单元测试依赖于实际磁盘上的实际文件来测试。

因此,我试图使用模拟对象注入I / O依赖项,并验证该对象在我的测试期间的行为。我无法弄清楚如何使用I / O对象以在我的基于模拟的单元测试和“真实代码”调用时均使用的语法。

我该如何更改代码(尽可能是load()方法),以便在从任一单元测试调用时执行其工作(没有模拟的临时测试,直到我弄清楚为止 - 它类似于稍后实际调用要测试的方法的代码)?

注意1:如果运行这些测试,请记住,“没有模拟”测试期望在磁盘上有一个文件,其文件名与VALID_FILENAME匹配。

注意2:我考虑过使用pcall的try / catch-like行为来执行一行或另一行(参见storageManager.lua第11和12行)。假设这是可能的,它感觉像一个黑客,并且它捕获我以后可能想要抛出的错误(从load())中。如果您看不到替代方案,请详细解释此选项。

require "StorageManager"
 require "lunity"
 require "lemock"
 module("storageManager", package.seeall, lunity)

 VALID_FILENAME = "storageManagerTest.dat"

 function setup()
     mc = lemock.controller()
 end

 function test_load_reads_file_properly()
     io_mock = mc:mock()
     file_handle_mock = mc:mock()
     io_mock:open(VALID_FILENAME, "r");mc:returns(file_handle_mock)
     file_handle_mock:read("*all")
     file_handle_mock:close()
     mc:replay()
     storageManager = StorageManager:new{ io = io_mock }
     storageManager:load(VALID_FILENAME)
     mc:verify()
 end

 function test_load_reads_file_properly_without_mock()
     storageManager = StorageManager:new()
     storageManager:load(VALID_FILENAME)
 end

 runTests{useANSI = false}
StorageManager = {}

 function StorageManager.new (self,init)
     init = init or { io=io } -- I/O dependency injection attempt
     setmetatable(init,self)
     self.__index = self
     return init
 end

 function StorageManager:load(filename)
     file_handle = self['io'].open(self['io'], filename, "r") -- works w/ mock
     -- file_handle = io.open(filename, "r") -- works w/o mock
     result = file_handle:read("*all")
     file_handle:close()
     return result
 end

这些类通过了两个测试。非常感谢RBerteig。

require "admin.StorageManager"
 require "tests.lunity"
 require "lib.lemock"
 module("storageManager", package.seeall, lunity)

 VALID_FILENAME = "storageManagerTest.dat"

 function setup()
     mc = lemock.controller()
 end

 function test_load_reads_file_properly()
     io_mock = mc:mock()
     file_handle_mock = mc:mock()
     io_mock.open(VALID_FILENAME, "r");mc:returns(file_handle_mock)
     file_handle_mock:read("*all")
     file_handle_mock:close()
     mc:replay()
     local saved_io = _G.io
     _G.io = io_mock
     package.loaded.io = io_mock
     storageManager = StorageManager:new()
     storageManager:load(VALID_FILENAME)
     _G.io = saved_io
     package.loaded.io = saved_io
     mc:verify()
 end

 function test_load_reads_file_properly_without_mock()
     storageManager = StorageManager:new()
     storageManager:load(VALID_FILENAME)
 end

 runTests{useANSI = false}
StorageManager = {}

 function StorageManager.new (self,init)
     init = init or {}
     setmetatable(init,self)
     self.__index = self
     return init
 end

 function StorageManager:load(filename)
     file_handle = io.open(filename, "r")
     result = file_handle:read("*all")
     file_handle:close()
     return result
 end

原文链接 https://stackoverflow.com/questions/1021125

点赞
stackoverflow用户68204
stackoverflow用户68204

我认为您让问题变得比必须的更困难了。

假设模块 storageManager.lua 没有本地化 io 模块本身,那么您只需要在运行测试时用您的模拟对象替换全局的 io

如果模块对 io 对象进行了本地化以提高性能,则需要在加载模块之前注入新的 io 值。这可能意味着您需要在测试用例设置中进行对 require 的调用(以及配套的清理,从 package.loaded_G 中删除模块的所有痕迹),以便在不同的测试用例中进行不同的模拟。

WinImage

编辑:

通过本地化模块来提高性能,我指的是 Lua 的惯用法,即将模块的方法复制到模块名称空间中的局部变量中。例如:

- somemodule.lua
require "io"
require "math"

-- 复制 io 和 math 到本地变量
local io, math = io, math

-- 开始模块本身,注意没有使用 package.seeall,因此全局变量
-- 在此时之后不可见
module(...)

function doMathAndIo()
    -- 从这里开始做一些有趣的事情
end

如果您这样做,对库存模块 iomath 的引用是在执行 require "somemodule" 时进行的。在调用 require() 之后将其中任何一个模块替换为模拟版本将是无效的。

要有效地模拟使用此惯用法的模块,您必须在调用 require() 之前放置模拟对象。

以下是我如何在测试用例中替换 io 对象的过程:

function test_load_reads_file_properly()
    io_mock = mc:mock()
    file_handle_mock = mc:mock()
    io_mock:open(VALID_FILENAME, "r");mc:returns(file_handle_mock)
    file_handle_mock:read("*all")
    file_handle_mock:close()
    mc:replay()

    local saved_io = _G.io
    _G.io = io_mock
    package.loaded.io = io_mock
    storageManager = StorageManager:new{ }
    storageManager:load(VALID_FILENAME)
    _G.io = saved_io
    package.loaded.io = saved_io
    mc:verify()
end

我可能没有在恰当的时刻恢复真实对象,并且这是未经测试的,但它应该能指引您正确的方向。

2009-06-20 07:51:05