Deuxième injection de dépendance d'E / S
-
06-07-2019 - |
Question
Je suis un novice Lua. Je suis en train de tester le code Lua 5.1 à l'aide de Lunity . et LeMock .
Ma classe est StorageManager. Je suis en train de tester sa méthode load (), qui charge les fichiers à partir du disque. Je ne veux pas que mes tests unitaires dépendent des fichiers réels sur le disque pour le tester.
J'essaie donc d'injecter la dépendance d'E / S avec un objet fictif et de vérifier le comportement de cet objet lors de mon test. Je n'arrive pas à comprendre comment utiliser l'objet d'E / S avec une syntaxe qui fonctionne lorsqu'il est appelé à la fois par mon test unitaire fictif et par le "code réel".
Comment puis-je changer le code (méthode load (), de préférence) pour qu'il remplisse son rôle lorsqu'il est appelé à partir de l'un des tests unitaires (celui sans maquette est temporaire jusqu'à ce que je sache ce qu'il en est - il ressemble au code qui appellera plus tard la méthode testée)?
Remarque 1: Si vous exécutez ces tests, rappelez-vous que l'option "sans maquette". test attend un fichier sur le disque dont le nom de fichier correspond à VALID_FILENAME.
Note2: J'ai envisagé d'utiliser le comportement try / catch de pcall pour exécuter une ligne ou l'autre (voir storageManager.lua lignes 11 et 12). En supposant que ce soit même possible, cela ressemble à un hack, et cela piège les erreurs que je pourrais éventuellement vouloir jeter plus tard (out of load ()). Veuillez expliquer cette option si vous ne voyez aucune 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
Modifier:
Ces classes passent les deux tests. Un grand merci 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
La solution
Je pense que vous rendez le problème plus difficile qu'il ne le devrait.
En supposant que le module storageManager.lua ne localise pas lui-même le module io
, il vous suffit alors de remplacer le io
global par votre objet fictif pendant l'exécution. le test.
Si le module localise l'objet io
pour améliorer les performances, vous devez alors injecter la nouvelle valeur de io
avant de charger le module. Cela peut vouloir dire que vous devez appeler require
dans la configuration du scénario de test (et un nettoyage correspondant qui supprime toutes les traces du module de package.loaded
et < code> _G ) afin qu'il puisse être simulé différemment dans différents cas de test.
WinImage
Modifier:
En localisant un module pour la performance, j'entends le langage Lua qui consiste à copier les méthodes du module dans des variables locales dans l'espace de noms 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, les références aux modules de stock io
et math
sont faites au moment où nécessite que "somemodule" soit
réalisé. Remplacer l’un de ces modules après l’appel de require ()
par une version simulée ne sera pas effectif.
Pour simuler efficacement un module utilisé avec cet idiome, vous devez placer l'objet factice à la place avant l'appel de require ()
.
Voici comment procéder pour remplacer l'objet io pendant la durée de l'appel dans un scénario 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 restaure peut-être pas l'objet réel au bon moment, et cela n'a pas été testé, mais il devrait vous indiquer la bonne direction.