حقن تبعية الإدخال/الإخراج لوا
-
06-07-2019 - |
سؤال
أنا مبتدئ لوا.أقوم باختبار وحدة كود Lua 5.1 باستخدام لونيتي و ليموك.
صفي هو StorageManager.أنا أقوم باختبار طريقة التحميل () الخاصة بها، والتي تقوم بتحميل الملفات من القرص.لا أريد أن تعتمد اختبارات وحدتي على الملفات الفعلية الموجودة على القرص الفعلي لاختبار ذلك.
لذا، أحاول حقن تبعية الإدخال/الإخراج بكائن وهمي والتحقق من سلوك هذا الكائن أثناء الاختبار.لا يمكنني معرفة كيفية استخدام كائن الإدخال/الإخراج مع بناء جملة يعمل عند استدعائه بواسطة اختبار الوحدة الوهمي الخاص بي و"الرمز الحقيقي".
كيف يمكنني تغيير الكود (يُفضل استخدام طريقة التحميل () بحيث يقوم بعمله عند استدعائه من أي اختبار للوحدة (يعد الكود الذي لا يحتوي على النموذج مؤقتًا حتى أكتشف ذلك - إنه يشبه الكود الذي سوف في وقت لاحق استدعاء الطريقة قيد الاختبار)؟
ملاحظة 1:إذا قمت بتشغيل هذه الاختبارات، فتذكر أن اختبار "بدون وهمية" يتوقع وجود ملف على القرص يتطابق اسمه مع VALID_FILENAME.
ملاحظة 2:فكرت في استخدام سلوك المحاولة/الالتقاط الخاص بـ pcall لتنفيذ سطر واحد أو آخر (راجع سطر 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}
مدير التخزين. لوا:
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
المحلول
أعتقد أنك تجعل المشكلة أكثر صعوبة مما ينبغي.
على افتراض أن وحدة StorageManager.lua لا تقوم بنفسها بترجمة ملف io
الوحدة، فكل ما عليك فعله هو استبدال الوحدة global 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
و 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
ربما لا أقوم باستعادة الكائن الحقيقي في اللحظة المناسبة تمامًا، وهذا لم يتم اختباره، لكنه يجب أن يوجهك في الاتجاه الصحيح.