تقلل الاختبارات المكتوبة جيدًا بشكل كبير من خطر "كسر" التطبيق عند إضافة ميزة جديدة أو إصلاح خطأ. في الأنظمة المعقدة التي تتكون من عدة مكونات مترابطة ، يكون الأصعب هو اختبار الأرضية المشتركة.
في هذه المقالة ، سأتحدث عن كيف واجهنا صعوبة كتابة اختبارات جيدة عند تطوير مكون على Go وكيف تمكنا من حل هذه المشكلة باستخدام مكتبة RSpec في Ruby on Rails.
إضافة الانتقال إلى مكدس التكنولوجيا للمشروع
يمكن تقسيم أحد المشاريع التي طورها eTeam ، حيث أعمل ، إلى: لوحة المشرف ، وحساب المستخدم ، ومنشئ التقارير ومعالجة الطلبات من الخدمات المختلفة التي تم دمجها معها.
الجزء المسؤول عن معالجة الطلبات هو الأهم ، لذلك أردت أن أجعلها موثوقة وبأسعار معقولة قدر الإمكان. كونها جزءًا من تطبيق مترابط ، فقد جازفت في الحصول على خطأ عند تغيير أقسام التعليمات البرمجية غير المرتبطة بها. كان هناك أيضًا خطر انخفاض المعالجة عند تحميل مكونات التطبيق الأخرى. عدد العاملين في Ngnix لكل تطبيق محدود ، ومع نمو الحمل ، على سبيل المثال ، فتح العديد من الصفحات الثقيلة في لوحة المشرف ، وتوقف العمال الحرون وتباطأ طلب المعالجة ، أو حتى انخفض.
هذه المخاطر ، وكذلك نضج هذا النظام (لأشهر لم يكن عليه إجراء تغييرات) جعلته مرشحًا مثاليًا للانفصال في خدمة منفصلة.
تقرر كتابة هذه الخدمة المنفصلة على Go. كان عليه مشاركة الوصول إلى قاعدة البيانات مع تطبيق ريلز. بقيت المسؤولية عن التغييرات المحتملة في هيكل الجدول مع Rails. من حيث المبدأ ، يعمل مثل هذا المخطط مع قاعدة بيانات مشتركة بشكل جيد ، بينما يوجد تطبيقان فقط. بدا مثل هذا:

تمت كتابة الخدمة ونشرها في حالات منفصلة عن Rails. الآن ، عند نشر تطبيقات Rails ، لا داعي للقلق من أنها ستؤثر على معالجة الاستعلام. قبلت الخدمة طلبات HTTP مباشرة ، دون Ngnix ، استهلكت القليل من الذاكرة ، كانت بطريقة ما أضيق الحدود.
المشكلة في اختبارات وحدتنا في Go
تم تنفيذ اختبارات الوحدة في تطبيق Go ، وتم إغلاق جميع استعلامات قاعدة البيانات الموجودة فيها. من بين الحجج الأخرى لصالح مثل هذا الحل ما يلي: تطبيق ريلز الرئيسي هو المسؤول عن هيكل قاعدة البيانات ، وبالتالي فإن التطبيق الذهاب لا "يمتلك" المعلومات الخاصة بإنشاء قاعدة بيانات اختبار. تألفت طلبات المعالجة للنصف من منطق الأعمال ونصف العمل مع قاعدة البيانات ، وتم إغلاق هذا النصف تمامًا. يبدو Moki in Go "قابل للقراءة" أقل من روبي. عند إضافة وظيفة جديدة لقراءة البيانات من قاعدة البيانات ، كان يلزم إضافة moki لها في مجموعة الاختبارات الساقطة التي عملت من قبل. ونتيجة لذلك ، كانت اختبارات الوحدة غير فعالة وهشة للغاية.
طريقة الحل
للقضاء على هذه العيوب ، تقرر تغطية الخدمة باختبارات وظيفية موجودة في تطبيق Rails واختبار الخدمة على Go كمربع أسود. كمربع أبيض ، لا يزال لا يعمل ، لأنه من الياقوت ، حتى مع كل الرغبة ، سيكون من المستحيل التدخل في الخدمة ، على سبيل المثال ، الحصول على بعض الطريقة المبللة للتحقق مما إذا كان يتم استدعاؤها. وهذا يعني أيضًا أنه من المستحيل أيضًا قفل الطلبات المرسلة من قبل الخدمة المختبرة ، لذلك ، هناك حاجة إلى تطبيق آخر لالتقاطها وتسجيلها. شيء مثل RequestBin ، ولكنه محلي. لقد كتبنا بالفعل أداة مشابهة ، لذلك استخدمناها.
لقد ظهر المخطط التالي:
- يقوم rspec بتجميع الخدمة وبدء تشغيلها ، وتمريرها ، والتي تحتوي على الوصول إلى قاعدة الاختبار ومنفذ معين لتلقي طلبات HTTP ، على سبيل المثال 8082
- يتم تشغيل أداة مساعدة لتسجيل طلبات HTTP المستلمة عليها على المنفذ 8083
- نكتب اختبارات عادية على RSpec ، أي إنشاء البيانات اللازمة في قاعدة البيانات وإرسال طلب إلى localhost: 8082 ، كما لو كانت لخدمة خارجية ، على سبيل المثال باستخدام HTTParty
- استجابة parsim التحقق من التغييرات في قاعدة البيانات ؛ نحصل على قائمة الطلبات المسجلة من "RequestBin" والتحقق منها.
تفاصيل التنفيذ:
الآن حول كيفية تنفيذها. لغرض العرض التوضيحي ، دعنا نسمي الخدمة التي تم اختبارها: "TheService" وإنشاء غلاف لها:
تحسبًا فقط ، سأجري حجزًا في Rspec يجب تهيئته للتحميل التلقائي للملفات من مجلد "الدعم":
Dir[Rails.root.join('spec/support/**/*.rb')].each {|f| require f}
طريقة البدء:
- يقرأ من تكوين منفصل المسار إلى مصادر الخدمة والمعلومات اللازمة للتشغيل. لأن قد تختلف هذه المعلومات عن مطورين مختلفين ، هذا التكوين مستبعد من Git. يحتوي التكوين نفسه على الإعدادات اللازمة لبدء البرنامج. توجد هذه التكوينات غير المتجانسة في مكان واحد فقط حتى لا تنتج ملفات إضافية.
- يجمع ويدير البرنامج من خلال "go run {path to main.go} {path to config}"
- عند اقتراع كل ثانية ، ينتظر حتى يصبح البرنامج قيد التشغيل جاهزًا لقبول الطلبات
- يتذكر معرف العملية حتى لا يتم إعادة تشغيله ويكون قادرًا على إيقافه.
التكوين نفسه:
#/spec/support/the_service_config.yml server: addr: 127.0.0.1:8082 db: dsn: dbname=project_test sslmode=disable user=postgres password=secret redis: url: redis://127.0.0.1:6379/1 rails: main_go: /home/me/go/src/github.com/company/theservice/main.go recorder_addr: 127.0.0.1:8083 env: PATH: '/home/me/.gvm/gos/go1.10.3/bin' GOROOT: '/home/me/.gvm/gos/go1.10.3' GOPATH: '/home/me/go'
طريقة التوقف ببساطة توقف العملية. الشيء الجديد هو أن ruby يقوم بتشغيل الأمر "go run" الذي يقوم بتشغيل الملف الثنائي المترجم في عملية تابعة له معرّف غير معروف. إذا توقفت للتو عن بدء العملية من الياقوت ، فلن تتوقف العملية الفرعية تلقائيًا وسيظل المنفذ مشغولاً. لذلك ، يحدث التوقف بواسطة معرف مجموعة العمليات:
الآن سنقوم بإعداد نص Shared_context حيث نحدد المتغيرات الافتراضية ، ونبدأ TheService إذا لم يتم تشغيله ، وقمنا مؤقتًا بتعطيل VCR (من وجهة نظره ، نتحدث إلى خدمة خارجية ، لكن الأمر ليس كذلك بالنسبة لنا الآن):
والآن يمكنك البدء في كتابة المواصفات بأنفسهم:
يمكن أن تقدم الخدمة طلبات HTTP الخاصة بها إلى الخدمات الخارجية. باستخدام التكوين ، نقوم بإعادة التوجيه إلى أداة مساعدة محلية تكتبها. هناك أيضًا غلاف لبدء وإيقاف ، وهو مشابه لفئة "TheServiceControl" ، باستثناء أنه يمكن ببساطة بدء تشغيل الأداة بدون تجميع.
كعك إضافي
تمت كتابة تطبيق Go بحيث يتم عرض جميع السجلات ومعلومات التصحيح في STDOUT. عند بدء الإنتاج ، يتم إرسال هذا الإخراج إلى ملف. وعند إطلاقه من Rspec ، يتم عرضه في وحدة التحكم ، مما يساعد كثيرًا عند تصحيح الأخطاء.
إذا تم تشغيل المواصفات بشكل انتقائي ، والتي لا تحتاج إليها TheService ، فلن تبدأ.
لتجنب إضاعة الوقت في تطوير الخدمة في كل مرة تقوم فيها بإعادة تشغيل المواصفات عند التطوير ، يمكنك بدء الخدمة يدويًا في المحطة الطرفية وعدم إيقاف تشغيلها. إذا لزم الأمر ، يمكنك حتى تشغيله في IDE في وضع التصحيح ، ومن ثم ستقوم المواصفات بإعداد كل ما تحتاجه ، ورمي طلب خدمة ، وسوف تتوقف ويمكنك أن تنحل دون ضجة. وهذا يجعل منهج TDD مريح للغاية.
الاستنتاجات
يعمل هذا المخطط لمدة عام تقريبًا ولم يفشل أبدًا. المواصفات أكثر قابلية للقراءة من اختبارات الوحدة على Go ، ولا تعتمد على معرفة الهيكل الداخلي للخدمة. إذا احتجنا ، لسبب ما ، إلى إعادة كتابة الخدمة بلغة أخرى ، فلن نضطر إلى تغيير المواصفات ، باستثناء الغلاف ، الذي يحتاج فقط إلى بدء خدمة الاختبار بأمر آخر.