2009-06-20 5 views
0

Je suis un novice Lua. Je suis unité testant le code Lua 5.1 en utilisant Lunity et LeMock.Lua I/O injection de dépendance

Ma classe est StorageManager. Je suis unité de tester sa méthode load(), qui charge les fichiers à partir du disque. Je ne veux pas que mes tests unitaires dépendent de fichiers réels sur le disque réel pour le tester. Donc, j'essaie d'injecter la dépendance d'E/S avec un objet simulé et de vérifier le comportement de cet objet pendant mon test. Je n'arrive pas à comprendre comment utiliser l'objet d'E/S avec une syntaxe qui fonctionne lorsqu'elle est appelée à la fois par mon test unitaire basé sur des simulacres et par le "vrai code". Comment puis-je changer le code (la méthode load(), de préférence) afin qu'il fasse son travail lorsqu'il est appelé à partir de l'un des tests unitaires (celui sans le simulacre est temporaire jusqu'à ce que je compris cela - il ressemble le code qui appellera plus tard la méthode sous test)?

Remarque1: Si vous exécutez ces tests, rappelez-vous que le test "sans simulation" attend un fichier sur le disque dont le nom de fichier correspond à VALID_FILENAME.

Note2: J'ai pensé utiliser le comportement try/catch de pcall pour exécuter une ligne ou l'autre (voir storageManager.lua lignes 11 & 12). En supposant que c'est même possible, cela ressemble à un hack, et il piège les erreurs que je pourrais éventuellement vouloir lancer (out of load()). S'il vous plaît exposer sur cette option si vous ne voyez pas d'alternative.

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:

Ces classes passent deux tests. Merci beaucoup au 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 

Répondre

1

Je pense que vous faites le problème plus difficile qu'il doit être. En supposant que le module storageManager.lua ne localise pas lui-même le module io, tout ce que vous devez faire est de remplacer le io global par votre objet fantôme lors de l'exécution du test.

Si le module localise l'objet io pour les performances, vous devez injecter la nouvelle valeur io avant de charger le module. Cela peut signifier que vous devez faire l'appel à require une partie de l'installation du scénario de test (et un nettoyage correspondant qui supprime toutes les traces du module de package.loaded et _G) afin qu'il puisse être moqué différemment dans différents cas de test. WinImage

Edit:

En localisant un module pour une performance que je veux dire l'idiome Lua de copier les méthodes du module dans des variables locales dans l'espace de nom du module. Par exemple:

 
-- 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 

Si vous faites cela, sont faits au moment où require "somemodule" est exécuté les références aux modules stock io et math. Le remplacement de l'un de ces modules après l'appel à require() avec une version simulée ne sera pas efficace.

Pour se moquer efficacement d'un module qui est utilisé avec cet idiome, vous devez avoir l'objet maquette en place avant l'appel à require().

Voici une façon dont j'irais de remplacer l'objet io pendant toute la durée de l'appel dans un cas de test:

 

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 

Je ne peux pas être la restauration de l'objet réel exactement au bon moment, et ce n'est pas testé, mais il devrait vous diriger dans la bonne direction.

+0

Je ne veux pas dire (ou savoir comment) «localiser l'objet io pour la performance», mais je veux «remplacer l'io global par [mon] objet fantôme pendant l'exécution du test». Je pensais que je faisais ça sur storageManager.lua: 4? Comment peut-on "remplacer le io global"? Pouvez-vous fournir (ou me diriger vers) un exemple de code? – lance

+0

Votre édition m'a donné ce dont j'avais besoin. J'ai modifié mon code modifié dans la question d'origine. Merci beaucoup. – lance