Внедрение зависимости Lua I / O - PullRequest
0 голосов
/ 20 июня 2009

Я новичок Lua. Я тестирую код Lua 5.1 с использованием Lunity и LeMock .

Мой класс - StorageManager. Я тестирую его метод load (), который загружает файлы с диска. Я не хочу, чтобы мои модульные тесты зависели от реальных файлов на реальном диске, чтобы это проверить.

Итак, я пытаюсь внедрить зависимость ввода-вывода с помощью фиктивного объекта и проверить поведение этого объекта во время теста. Я не могу понять, как использовать объект ввода-вывода с синтаксисом, который работает, когда он вызывается как моим модульным тестом, основанным на имитации, так и «реальным кодом».

Как я могу изменить код (предпочтительно метод load ()), чтобы он выполнял свою работу при вызове из любого модульного теста (тот, у которого нет макета, является временным, пока я не выясню это - он похож код, который позже будет вызывать тестируемый метод)?

Примечание 1. Если вы запустите эти тесты, помните, что тест «без макета» ожидает файл на диске, имя файла которого совпадает с VALID_FILENAME.

Примечание 2: Я думал об использовании pcall-подобного поведения try / catch для выполнения одной или другой строки (см. StorageManager.lua, строки 11 и 12). Предполагая, что это даже возможно, это похоже на хак, и это ловит ошибки, которые я мог бы позже хотеть выбросить (вне нагрузки ()). Пожалуйста, разъясните эту опцию, если вы не видите альтернативы.

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

Edit:

Эти классы проходят оба теста. Большое спасибо 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 Ответ

1 голос
/ 20 июня 2009

Я думаю, что вы делаете проблему более сложной, чем она должна быть.

Если предположить, что модуль storageManager.lua сам не локализует модуль io, то все, что вам нужно сделать, - это заменить глобальный io на ваш фиктивный объект во время выполнения теста.

Если модуль локализует объект io для повышения производительности, вам нужно будет ввести новое значение io перед загрузкой модуля. Это может означать, что вам нужно сделать вызов require частью настройки тестового набора (и соответствующей очистки, которая удаляет все следы модуля из package.loaded и _G), чтобы его можно было по-разному смоделировать в разных контрольные примеры. WinImage

Edit:

Под локализацией модуля для производительности я подразумеваю идиому 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 и math будут сделаны в момент выполнения 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

Возможно, я не восстанавливаю реальный объект в нужный момент, и это не проверено, но оно должно указать вам правильное направление.

...