يقوم Gonkey باختبار خدماتنا المصغرة في Lamoda ، واعتقدنا أنه يمكن اختبارها لك ، لذلك وضعناها في مصدر مفتوح . إذا تم تنفيذ وظائف خدماتك في المقام الأول من خلال واجهة برمجة التطبيقات ، وتم استخدام JSON لتبادل البيانات ، فسيكون Gonkey بالتأكيد مناسبًا لك.

أدناه سأتحدث عنها بمزيد من التفصيل وأعرض مع أمثلة محددة كيفية استخدامها.
كيف ولد جونكي
لدينا أكثر من مائة خدمة ميكروية ، كل منها يحل مهمة محددة. جميع الخدمات لديها API. بالطبع ، بعضها أيضًا واجهة المستخدم ، ولكن ، مع ذلك ، فإن دورها الأساسي هو أن تكون مصدر بيانات للموقع أو تطبيقات الهاتف المحمول أو الخدمات الداخلية الأخرى ، مما يعني توفير واجهة برمجية .
عندما أدركنا أن هناك الكثير من الخدمات ، وبعد ذلك سيكون هناك المزيد منها ، قمنا بتطوير وثيقة داخلية تصف النهج القياسي لتصميم API وأخذنا Swagger كأداة وصف (وحتى كتبنا أدوات مساعدة لإنشاء رمز استنادًا إلى مواصفات swagger). إذا كنت مهتمًا بمعرفة المزيد حول هذا الموضوع ، فراجع حديث أندرو مع Highload ++.
الطريقة القياسية لتصميم API أدت بطبيعة الحال إلى فكرة النهج القياسي للاختبار. إليكم ما أردت تحقيقه:
- اختبر الخدمات من خلال واجهة برمجة التطبيقات ، لأنه يتم تنفيذ جميع وظائف الخدمة تقريبًا من خلالها
- القدرة على أتمتة إطلاق الاختبارات لدمجها في عملية CI / CD ، كما يقولون ، "تشغيل عن طريق زر"
- يجب أن تكون اختبارات الكتابة قابلة للتحويل ، أي أنه لا يمكن للمبرمجين فقط كتابة الاختبارات ، ومن الأفضل أن يكون الشخص غير مطلع على البرمجة.
هكذا ولد غونكي.
إذن ما هذا؟
Gonkey هي مكتبة (للمشروعات في Golang) وهي أداة مساعدة لوحدة التحكم (للمشاريع بأي لغات وتقنيات) ، والتي يمكنك من خلالها إجراء اختبار وظيفي واختبار الانحدار للخدمات من خلال الوصول إلى API الخاصة بهم وفقًا لبرنامج نصي محدد مسبقًا. يتم وصف البرامج النصية للاختبار في ملفات YAML.
ببساطة ، يمكن لـ Gonkey:
- قصف خدمتك بطلبات HTTP وتأكد من أن ردودها كما هو متوقع. يفترض أن JSON يستخدم في الطلبات والردود ، ولكن على الأرجح ، سيعمل على حالات بسيطة مع إجابات بتنسيق مختلف ؛
- إعداد قاعدة البيانات للاختبار عن طريق ملء البيانات من تركيبات (المحددة أيضا في ملفات YAML) ؛
- محاكاة استجابات الخدمات الخارجية باستخدام mocks (تتوفر هذه الميزة فقط إذا قمت بتوصيل Gonkey كمكتبة) ؛
- إعطاء نتائج الاختبار إلى وحدة التحكم أو إنشاء تقرير Allure.
مستودع المشروع
صورة عامل الميناء
مثال على اختبار الخدمة مع Gonkey
لكي لا أميل إلى عبء النص ، أريد الانتقال من الكلمات إلى الأفعال ، واختبار واجهة برمجة التطبيقات (API) هنا وإخبار وإظهار كيفية كتابة نصوص الاختبار على طول الطريق.
لنرسم خدمة صغيرة على Go ستحاكي عمل إشارة المرور. يخزن لون الإشارة الحالية: الأحمر أو الأصفر أو الأخضر. يمكنك الحصول على لون الإشارة الحالي أو تعيين لون جديد من خلال واجهة برمجة التطبيقات.
شفرة المصدر الكاملة لل main.go هنا .
قم بتشغيل البرنامج:
go run .
رسم سريع للغاية في 15 دقيقة! بالتأكيد كان مخطئًا في مكان ما ، لذلك سنكتب اختبارًا ونتحقق منه.
تنزيل وتشغيل Gonkey:
mkdir -p tests/cases docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080
يقوم هذا الأمر ببدء تشغيل الصورة باستخدام gonkey خلال عامل النقل ، ويقوم بتحديث دليل الاختبارات / الحالات داخل الحاوية ويبدأ gonkey مع معلمات اختبارات / الحالات / -host الاختبارات.
إذا كنت لا تحب نهج عامل الميناء ، فسيكون بديل لمثل هذا الأمر هو الكتابة:
go get github.com/lamoda/gonkey go run github.com/lamoda/gonkey -tests tests/cases -host localhost:8080
أطلقت وحصلت على النتيجة:
Failed tests: 0/0
لا اختبارات - لا شيء للتحقق. دعنا نكتب الاختبار الأول. أنشئ ملف اختبارات / الحالات / light_get.yaml مع الحد الأدنى من المحتويات:
- name: WHEN currentLight is requested MUST return red method: GET path: /light/get response: 200: > { "currentLight": "red" }
على المستوى الأول قائمة. هذا يعني أننا قد وصفنا حالة اختبار واحدة ، ولكن يمكن أن يكون هناك الكثير منها في الملف. معا يشكلون سيناريو الاختبار. وبالتالي ، ملف واحد - النصي واحد. يمكنك إنشاء أي عدد من الملفات باستخدام البرامج النصية للاختبار ، إذا كان ذلك مناسبًا ، قم بترتيبها في الدلائل الفرعية - يقرأ gonkey جميع ملفات yaml و yml من الدليل المنقول ويكون متكررًا بشكل أعمق.
يصف الملف أدناه تفاصيل الطلب الذي سيتم إرساله إلى الخادم: الطريقة ، المسار. أقل من ذلك هو رمز الاستجابة (200) وهيئة الاستجابة التي نتوقعها من الخادم.
يتم وصف تنسيق الملف الكامل في README .
تشغيل مرة أخرى:
docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080
النتيجة:
Name: WHEN currentlight is requested MUST return red Request: Method: GET Path: /light/get Query: Body: <no body> Response: Status: 200 OK Body: {} Result: ERRORS! Errors: 1) at path $ values do not match: expected: { "currentLight": "red" } actual: {} Failed tests: 1/1
خطأ! كان من المتوقع أن يكون هناك هيكل به الحقل currentLight ، ويتم إرجاع بنية فارغة. هذا سيء المشكلة الأولى هي أن النتيجة تم تفسيرها كسلسلة ، وهذا يدل على حقيقة أنه ، كمكان للمشكلة ، سلط جونكي الضوء على الإجابة بأكملها دون أي تفاصيل:
expected: { "currentLight": "red" }
السبب بسيط: لقد نسيت أن تدل الخدمة في الرد على التطبيق / json لنوع المحتوى. نصلح:
نعيد تشغيل الخدمة ونجري الاختبارات مرة أخرى:
Name: WHEN currentlight is requested MUST return red Request: Method: GET Path: /light/get Query: Body: <no body> Response: Status: 200 OK Body: {} Result: ERRORS! Errors: 1) at path $ key is missing: expected: currentLight actual: <missing>
عظيم ، هناك تقدم. يتعرف Gonkey الآن على البنية ، لكنه لا يزال غير صحيح: الإجابة فارغة. السبب في ذلك هو أنني استخدمت حقل "لايت" غير قابل للتصدير في تعريف النوع:
في Go ، يعتبر حقل البنية المسمى بحرف صغير غير قابل للتصدير ، أي أنه يتعذر الوصول إليه من الحزم الأخرى. لا يرى متسلسل JSON ولا يمكنه تضمينه في الاستجابة. نصحح: نجعل الحقل بحرف كبير ، مما يعني أنه يتم تصديره:
أعد تشغيل الخدمة. تشغيل الاختبارات مرة أخرى.
Failed tests: 0/1
لقد مرت الاختبارات!
سنكتب نصًا آخر سيختبر طريقة الإعداد. املأ اختبارات الملفات / الحالات / light_set.yaml بالمحتويات التالية:
- name: WHEN set is requested MUST return no response method: POST path: /light/set request: > { "currentLight": "green" } response: 200: '' - name: WHEN get is requested MUST return green method: GET path: /light/get response: 200: > { "currentLight": "green" }
يحدد الاختبار الأول قيمة جديدة لإشارة المرور ، بينما يتحقق الثاني من الحالة للتأكد من تغيرها.
قم بإجراء الاختبارات بنفس الأمر:
docker run -it -v $(pwd)/tests:/tests lamoda/gonkey -tests tests/cases -host host.docker.internal:8080
النتيجة:
Failed tests: 0/3
نتيجة ناجحة ، لكن من حسن حظنا أن النصوص البرمجية قد نفذت بالترتيب الذي احتجنا إليه: أول light_get ، ثم light_set. ماذا سيحدث لو فعلوا العكس؟ دعنا نعيد تسمية:
mv tests/cases/light_set.yaml tests/cases/_light_set.yaml
وتشغيل مرة أخرى:
Errors: 1) at path $.currentLight values do not match: expected: red actual: green Failed tests: 1/3
أولاً ، تم تنفيذ المجموعة وتركت إشارة المرور في الحالة الخضراء ، لذا وجد اختبار المدى الحصول على خطأ - كان ينتظر اللون الأحمر.
تتمثل إحدى طرق التخلص من حقيقة أن الاختبار يعتمد على السياق في تهيئة الخدمة في بداية البرنامج النصي (أي ، في بداية الملف) ، وهو ما نقوم به بشكل عام في الاختبار المحدد - أولاً نضع القيمة المعروفة ، والتي يجب أن تنتج تأثيرًا معروفًا ، ثم تحقق من أن التأثير كان له تأثير.
هناك طريقة أخرى لإعداد سياق التنفيذ إذا كانت الخدمة تستخدم قاعدة البيانات وهي استخدام التركيبات مع البيانات التي يتم تحميلها في قاعدة البيانات في بداية البرنامج النصي ، وبالتالي تشكيل حالة يمكن التنبؤ بها من الخدمة التي يمكن التحقق منها. وصف وأمثلة العمل مع التركيبات في gonkey أريد طرحها في مقالة منفصلة.
في غضون ذلك ، أقترح الحل التالي. نظرًا لأننا في البرنامج النصي للمجموعة نختبر فعليًا طريقة light / set وطريقة light / get ، فنحن ببساطة لا نحتاج إلى البرنامج النصي light_get ، الذي يعتبر حساسًا للسياق. أحذفها وأعد تسمية البرنامج النصي المتبقي حتى يعكس الاسم الجوهر.
rm tests/cases/light_get.yaml mv tests/cases/_light_set.yaml tests/cases/light_set_get.yaml
كخطوة تالية ، أرغب في التحقق من بعض السيناريوهات السلبية للعمل مع خدمتنا ، على سبيل المثال ، هل ستعمل بشكل صحيح إذا أرسلت لون إشارة غير صحيح؟ أو لا ترسل اللون على الإطلاق؟
إنشاء اختبارات / حالات جديدة / light_set_get_negative.yaml النصي:
- name: WHEN set is requested MUST return no response method: POST path: /light/set request: > { "currentLight": "green" } response: 200: '' - name: WHEN incorrect color is passed MUST return error method: POST path: /light/set request: > { "currentLight": "blue" } response: 400: > incorrect current light: blue - name: WHEN color is missing MUST return error method: POST path: /light/set request: > {} response: 400: > incorrect current light: - name: WHEN get is requested MUST have color untouched method: GET path: /light/get response: 200: > { "currentLight": "green" }
إنه يتحقق مما يلي:
- عندما ينتقل اللون الخطأ ، يحدث خطأ ؛
- عندما لا ينتقل اللون ، يحدث خطأ ؛
- لا يؤدي تغيير اللون غير الصحيح إلى تغيير الحالة الداخلية لإشارة المرور.
تشغيل:
Failed tests: 0/6
كل شيء على ما يرام :)
ربط Gonkey كمكتبة
كما لاحظت ، نحن نختبر واجهة برمجة التطبيقات للخدمة ، ونستخلص بالكامل من اللغة والتقنيات التي كتبت بها. بالطريقة نفسها ، يمكننا اختبار أي واجهة برمجة تطبيقات عامة لا يمكننا الوصول إليها من رموز المصدر - يكفي إرسال الطلبات وتلقي الإجابات.
ولكن بالنسبة لتطبيقاتنا الخاصة المكتوبة ، هناك طريقة أكثر ملاءمة لتشغيل gonkey - لتوصيله بالمشروع كمكتبة. سيسمح هذا ، دون تجميع أي شيء مسبقًا - لا gonkey أو المشروع نفسه - بإجراء الاختبار عن طريق go test
ببساطة.
مع هذا النهج ، يبدو أننا نبدأ في كتابة اختبار وحدة ، وفي نص الاختبار نقوم بما يلي:
- تهيئة خادم الويب بنفس طريقة بدء الخدمة ؛
- تشغيل خادم تطبيق الاختبار على مضيف مضيف محلي وعشوائي ؛
- نسميه وظيفة من مكتبة gonkey ، ويمر بها عنوان خادم الاختبار وغيرها من المعالم. أدناه سأوضح هذا.
للقيام بذلك ، سوف يحتاج تطبيقنا إلى إعادة بيع بعض الشيء. وتتمثل النقطة الأساسية في جعل إنشاء الخادم وظيفة منفصلة ، لأننا نحتاج الآن إلى هذه الوظيفة في مكانين: عندما تبدأ الخدمة ، وحتى عند تشغيل اختبارات gonkey.
أضع الكود التالي في وظيفة منفصلة:
func initServer() {
الوظيفة الرئيسية ستكون مثل هذا:
func main() { initServer()
الرئيسية الذهاب تعديل الملف تماما .
هذا حررت أيدينا ، لذلك دعونا نبدأ في كتابة اختبار. أقوم بإنشاء ملف func_test.go:
func Test_API(t *testing.T) { initServer() srv := httptest.NewServer(nil) runner.RunWithTesting(t, &runner.RunWithTestingParams{ Server: srv, TestsDir: "tests/cases", }) }
هنا هو ملف func_test.go الكامل .
هذا كل شئ! نتحقق من:
go test ./...
النتيجة:
ok github.com/lamoda/gonkey/examples/traffic-lights-demo 0.018s
لقد مرت الاختبارات. إذا كان لدي كل من اختبارات الوحدة واختبارات gonkey ، فسيتم تشغيلهما معًا - بشكل ملائم للغاية.
Allure عبارة عن تنسيق تقرير اختبار لعرض النتائج بطريقة واضحة وجميلة. يمكن لـ Gonkey تسجيل نتائج الاختبار بهذا التنسيق. تفعيل الجاذبية بسيط للغاية:
docker run -it -v $(pwd)/tests:/tests -w /tests lamoda/gonkey -tests cases/ -host host.docker.internal:8080 -allure
سيتم وضع التقرير في الدليل الفرعي لنتائج جاذبية دليل العمل الحالي (لهذا السبب قمت بتحديد -w / الاختبارات).
عند توصيل gonkey كمكتبة ، يتم تنشيط تقرير Allure عن طريق تعيين متغير بيئة إضافي GONKEY_ALLURE_DIR:
GONKEY_ALLURE_DIR="tests/allure-results" go test ./…
يتم تحويل نتائج الاختبار المسجلة في الملفات إلى تقرير تفاعلي بواسطة الأوامر:
allure generate allure serve
كيف يبدو التقرير:

استنتاج
في المقالات التالية ، سأتناول استخدام التركيبات في gonkey وعلى تقليد استجابات الخدمات الأخرى باستخدام mocks.
أدعوك لتجربة gonkey في مشاريعك ، والمشاركة في تطويره (طلبات البلياردو مرحب بها!) أو قم بتمييزها بنجمة على github إذا كان هذا المشروع مفيدًا لك في المستقبل.