Lua I / O依赖注入

时间:2009-06-20 07:14:16

标签: dependency-injection mocking lua

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

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

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

如何更改代码(最好是load()方法),以便在从单元测试调用时它将完成它的工作(没有模拟的那个是临时的,直到我弄明白它 - 它类似于以后实际调用被测方法的代码?)

注意1:如果您运行这些测试,请记住“无模拟”测试需要磁盘上的文件,其文件名与VALID_FILENAME匹配。

注2:我考虑过使用pcall的try / catch-like行为执行一行或另一行(参见storageManager.lua第11行和第12行)。假设这甚至可能,它感觉就像一个黑客,它捕获我以后可能抛出的错误(out of load())。如果你别无选择,请详细说明这个选项。

test_storageManager.lua:

 1 require "StorageManager"
 2 require "lunity"
 3 require "lemock"
 4 module("storageManager", package.seeall, lunity)
 5 
 6 VALID_FILENAME = "storageManagerTest.dat"
 7 
 8 function setup()
 9     mc = lemock.controller()
10 end
11 
12 function test_load_reads_file_properly()
13     io_mock = mc:mock()
14     file_handle_mock = mc:mock()
15     io_mock:open(VALID_FILENAME, "r");mc:returns(file_handle_mock)
16     file_handle_mock:read("*all")
17     file_handle_mock:close()
18     mc:replay()
19     storageManager = StorageManager:new{ io = io_mock }
20     storageManager:load(VALID_FILENAME)
21     mc:verify()
22 end
23 
24 function test_load_reads_file_properly_without_mock()
25     storageManager = StorageManager:new()
26     storageManager:load(VALID_FILENAME)
27 end
28 
29 runTests{useANSI = false}

storageManager.lua:

 1 StorageManager = {}
 2 
 3 function StorageManager.new (self,init)
 4     init = init or { io=io } -- I/O dependency injection attempt
 5     setmetatable(init,self)
 6     self.__index = self
 7     return init
 8 end
 9 
10 function StorageManager:load(filename)
11     file_handle = self['io'].open(self['io'], filename, "r") -- works w/ mock
12     -- file_handle = io.open(filename, "r") -- works w/o mock
13     result = file_handle:read("*all")
14     file_handle:close()
15     return result
16 end

修改

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

test_storageManager.lua

 1 require "admin.StorageManager"
 2 require "tests.lunity"
 3 require "lib.lemock"
 4 module("storageManager", package.seeall, lunity)
 5 
 6 VALID_FILENAME = "storageManagerTest.dat"
 7 
 8 function setup()
 9     mc = lemock.controller()
10 end
11
12 function test_load_reads_file_properly()
13     io_mock = mc:mock()
14     file_handle_mock = mc:mock()
15     io_mock.open(VALID_FILENAME, "r");mc:returns(file_handle_mock)
16     file_handle_mock:read("*all")
17     file_handle_mock:close()
18     mc:replay()
19     local saved_io = _G.io
20     _G.io = io_mock
21     package.loaded.io = io_mock
22     storageManager = StorageManager:new()
23     storageManager:load(VALID_FILENAME)
24     _G.io = saved_io
25     package.loaded.io = saved_io
26     mc:verify()
27 end
28
29 function test_load_reads_file_properly_without_mock()
30     storageManager = StorageManager:new()
31     storageManager:load(VALID_FILENAME)
32 end
33
34 runTests{useANSI = false}

storageManager.lua

 1 StorageManager = {}
 2 
 3 function StorageManager.new (self,init)
 4     init = init or {}
 5     setmetatable(init,self)
 6     self.__index = self
 7     return init
 8 end
 9 
10 function StorageManager:load(filename)
11     file_handle = io.open(filename, "r")
12     result = file_handle:read("*all")
13     file_handle:close()
14     return result
15 end

1 个答案:

答案 0 :(得分:1)

我认为你使问题变得更加困难。

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

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

修改

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

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

-- copy io and math to local variables
local io,math=io,math

-- begin the module itself, note that package.seeall is not used so globals are
-- not visible after this point
module(...)

function doMathAndIo()
    -- does something interesting here
end

如果执行此操作,则会在io执行时生成对库存模块mathrequire "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

我可能无法在恰当的时刻恢复真实物体,这是未经测试的,但它应该指向正确的方向。