عندما يعيق الواقع: لماذا نحتاج إلى mocks و fixtures في Go؟
\n\nتخيل أنك تختبر دالة ترسل إشعارًا عبر البريد الإلكتروني. كل تشغيل للاختبار هو رسالة حقيقية إلى صندوق البريد. بعد عشرة اختبارات، سيكون صندوقك ممتلئًا، وسيلعن زملاؤك البريد العشوائي. أو الأسوأ: الدالة تتصل بـ API خارجي مدفوع أو يعمل مرة واحدة يوميًا.
\n\nفي عالم Go (أو Golang، كما يُطلق عليه غالبًا) الاختبار ليس مجرد علامة في قائمة المهام. إنه أساس الموثوقية. ولكن لجعل الاختبارات سريعة ومستقلة، نحتاج إلى عزل الكود عن العالم الخارجي. وهنا يأتي دور كائنات mock (mocks) و fixtures.
\n\nفي هذه المقالة، سنشرح كيفية استبدال التبعيات (mocking) وإعداد البيانات (fixtures) بشكل صحيح في Go. ستتعلم كيفية كتابة اختبارات لا تفشل بسبب الشبكة أو قاعدة البيانات أو الوقت من اليوم. هيا بنا!
\n\nما هو Mock و Fixture في سياق Go؟
\n\nكائنات Mock: بدائل للأنظمة المعقدة
\n\nكائنات Mock هي "بدائل" برمجية تحاكي سلوك المكونات الحقيقية. بدلاً من استدعاء قاعدة بيانات حقيقية أو خدمة HTTP، نستخدم كائنًا يعيد استجابات محددة مسبقًا.
\n\nفي Go، تعتبر mocks مهمة بشكل خاص لأن اللغة تشجع العمل مع الواجهات (interfaces). إذا كان الكود الخاص بك يعتمد على واجهة، يمكنك بسهولة استبدال التنفيذ الحقيقي بتنفيذ اختباري.
\n\nFixtures: تجهيز المسرح
\n\nFixtures هي البيانات أو الحالة التي يجب إعدادها قبل بدء الاختبار. على سبيل المثال، إنشاء قاعدة بيانات مؤقتة، ملؤها بسجلات اختبارية، أو إعداد نظام الملفات.
\n\nعلى عكس mocks التي تستبدل السلوك، تقوم fixtures بتجهيز البيئة. إنها تجيب على السؤال: "ما الذي يجب أن يكون جاهزًا حتى يبدأ الاختبار؟"
\n\nكتابة mocks في Go: من البسيط إلى المعقد
\n\nأبسط طريقة: تنفيذ الواجهة يدويًا
\n\nلا تتطلب Go مكتبات خاصة لإنشاء mocks. إذا كان لديك واجهة، يمكنك كتابة تنفيذ اختباري لها يدويًا. هذا مثالي للمشاريع الصغيرة.
\n\nلنفترض أن لدينا واجهة لإرسال الإشعارات:
\n\npackage notifier\n\ntype Notifier interface {\n Send(to string, message string) error\n}\n\n// التنفيذ الحقيقي (ليس للاختبارات)\ntype EmailNotifier struct{}\n\nfunc (e *EmailNotifier) Send(to string, message string) error {\n // إرسال بريد إلكتروني حقيقي\n return nil\n}\n\nفي ملف الاختبار، ننشئ mock:
\n\npackage notifier\n\nimport "testing"\n\ntype MockNotifier struct {\n Called bool\n LastTo string\n LastMsg string\n}\n\nfunc (m *MockNotifier) Send(to string, msg string) error {\n m.Called = true\n m.LastTo = to\n m.LastMsg = msg\n return nil\n}\n\nfunc TestSendAlert(t *testing.T) {\n mock := &MockNotifier{}\n // نستخدم mock في الدالة التي تقبل Notifier\n SendAlert(mock, "user@test.com", "Hello!")\n \n if !mock.Called {\n t.Error("لم يتم استدعاء طريقة Send")\n }\n if mock.LastTo != "user@test.com" {\n t.Errorf("مستلم غير صحيح: %s", mock.LastTo)\n }\n}\n\nالإيجابيات: تحكم كامل، لا تبعيات.
\nالسلبيات: الكثير من الكود النمطي للواجهات المعقدة.
استخدام testify/mock: قوي ومريح
\n\nمكتبة testify هي المعيار الفعلي لـ mocks في Go. توفر إطارًا مرنًا لإنشاء mocks مع التحقق من الوسائط وعدد الاستدعاءات.
التثبيت:
\ngo get github.com/stretchr/testify\n\nمثال باستخدام testify/mock:
\n\npackage user\n\nimport (\n "testing"\n "github.com/stretchr/testify/mock"\n)\n\n// تعريف الواجهة\ntype UserRepository interface {\n GetUser(id int) (string, error)\n}\n\n// إنشاء هيكل mock\ntype