代码之家  ›  专栏  ›  技术社区  ›  lance

Lua I/O依赖注入

  •  0
  • lance  · 技术社区  · 15 年前

    我是个新手。我在单元测试Lua5.1代码使用 Lunity LeMock .

    我的班是StorageManager。我是单元测试它的load()方法,它从磁盘加载文件。我不希望我的单元测试依赖于实际磁盘上的实际文件来测试它。

    因此,我尝试将I/O依赖项注入到一个模拟对象中,并在测试期间验证该对象的行为。我不知道如何将I/O对象与我的模拟单元测试和“真正的代码”调用的语法一起使用。

    如何更改代码(最好是load()方法),以便在从任何一个单元测试中调用时(没有mock的是临时的,直到我弄清楚为止——它类似于稍后实际调用测试中的方法的代码)?

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

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

    测试存储管理器.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}
    

    存储管理器.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
    

    编辑:

    这些类通过了两个测试。多亏了阿尔贝特。

    测试存储管理器.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}
    

    存储管理器.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 回复  |  直到 15 年前
        1
  •  1
  •   RBerteig Keith Adler    15 年前

    我认为你使这个问题比必须的更困难。

    假设module storagemanager.lua本身没有本地化 io 模块,那么您所需要做的就是替换全局 输入输出 在运行测试时使用模拟对象。

    如果模块确实本地化了 输入输出 对象的性能,则需要注入 输入输出 在加载模块之前。这可能意味着你需要打电话给 require 测试用例设置的一部分(以及从中删除模块所有痕迹的匹配清理) package.loaded _G )以便在不同的测试用例中对其进行不同的模拟。 Win图像

    编辑:

    通过对模块进行性能本地化,我的意思是将模块的方法复制到模块名称空间中的局部变量中的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
    

    如果您这样做,那么对stock模块的引用 输入输出 math 就在那时 require "somemodule" 执行。调用后替换这些模块中的任何一个 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
    

    我可能没有在正确的时间恢复真实的物体,这是未经测试的,但它应该指向正确的方向。