ما هو اختبار BDD؟ إطار عمل تطوير قائم على السلوك
⚡ ملخص ذكي
يصف اختبار BDD سلوك التطبيق بلغة بسيطة تعتمد على المعطيات والحالات والنتائج، مما يتيح للمحللين والمطورين والمختبرين استخدام مواصفات موحدة. يطبق هذا الدليل العملي هذا النهج على اختبار واجهات برمجة تطبيقات REST باستخدام Behave. Python إطار عمل يشمل الإعداد، وملفات الميزات، وتنفيذ الخطوات، والتنفيذ، وإعداد التقارير.

ما هو اختبار BDD (التطوير الموجه بالسلوك)؟
اختبار BDD (التطوير المبني على السلوك). هي تقنية من تقنيات تطوير البرمجيات الرشيقة، وهي امتداد لتقنية تطوير البرمجيات الموجهة بالاختبار (TDD). في BDD، تُكتب حالات الاختبار بلغة طبيعية يمكن حتى لغير المبرمجين قراءتها.
كيف يعمل اختبار BDD؟
لنفترض أنك مكلف بإنشاء وحدة تحويل الأموال في تطبيق الخدمات المصرفية عبر الإنترنت.
هناك طرق متعددة لاختباره:
- يجب إجراء تحويل الأموال إذا كان هناك رصيد كافٍ في الحساب المصدر
- يجب أن يتم تحويل الأموال إذا كانت تفاصيل الحساب الوجهة صحيحة
- يجب أن تتم عملية تحويل الأموال إذا كانت كلمة مرور المعاملة / رمز RSA / بيانات المصادقة الأمنية التي أدخلها المستخدم صحيحة
- ينبغي إجراء تحويل الأموال حتى في أيام العطلات الرسمية.
- يجب أن يتم تحويل الأموال في تاريخ مستقبلي يحدده صاحب الحساب
استخدم سيناريو الاختبار يصبح الأمر أكثر تعقيدًا وتفصيلًا عند النظر في ميزات إضافية مثل تحويل مبلغ X لفترة زمنية مدتها Y يومًا أو شهرًا، أو إيقاف العملية.ping نقل مُجدول عندما يصل المجموع إلى Z، وهكذا.
يميل المطورون عموماً إلى تطوير الميزات وكتابة أكواد الاختبار لاحقاً. وكما هو واضح في الحالة المذكورة أعلاه، حالة الاختبار التطوير هنا معقد، لذا سيؤجل المطور الأمر. الاختبار حتى الإصدار، وعند هذه النقطة يتم إجراء اختبار سريع ولكنه غير فعال.
للتغلب على هذه المشكلة، تم ابتكار منهجية تطوير البرمجيات القائمة على السلوك (BDD). فهي تجعل عملية الاختبار بأكملها أسهل بالنسبة للمطور.
في BDD، كل ما تكتبه يجب أن يدخل فيه نظرا-متى-ثم خطوات. لنأخذ المثال نفسه أعلاه في BDD:
Given that a fund transfer module in net banking application has been developed And I am accessing it with proper authentication When I shall transfer with enough balance in my source account Or I shall transfer on a Bank Holiday Or I shall transfer on a future date And destination a/c details are correct And transaction password / rsa code / security authentication for the transaction is correct And press or click send button Then amount must be transferred And the event will be logged in log file
هذا النموذج سهل الكتابة والقراءة والفهم. يغطي جميع حالات الاختبار الممكنة لوحدة تحويل الأموال، ويمكن تعديله بسرعة لاستيعاب المزيد. كما أنه يُشبه توثيقًا حيًا للوحدة. ولأن منهجية تطوير البرمجيات القائمة على السلوك (BDD) انبثقت من منهجية تطوير البرمجيات القائمة على الاختبار (TDD)، فإن الفرق بينهما جدير بالتوضيح قبل الانتقال إلى الأدوات.
الفرق الرئيسي بين BDD و TDD
كلا الممارستين تعتمدان على الاختبار أولاً، وكلاهما يقلل من دورات التغذية الراجعة. ويختلفان في من يكتب الاختبارات، واللغة المستخدمة فيها، والطبقة التي تصفها من التطبيق.
| البعد | تطوير البرمجيات الموجه بالاختبار (TDD) | BDD (التطوير الموجه بالسلوك) |
|---|---|---|
| التركيز على | كيف تعمل وحدة البرمجة داخلياً | كيف يتصرف النظام من وجهة نظر المستخدم |
| اللغة | تأكيدات لغة البرمجة | سيناريوهات اللغة الطبيعية من نوع "معطى-عندما-إذن" |
| المؤلف الرئيسي | المطور | محلل أعمال، ومالك منتج، ومختبر، ومطور معًا |
| Readable من قبل غير المبرمجين | لا | نعم |
| النطاق النموذجي | مستوى الوحدة | الميزة، وواجهة برمجة التطبيقات، ومستوى القبول |
| الأدوات الشائعة | JUnit، pytest، NUnit | تصرف بشكل لائق، Cucumber، SpecFlow، JBehave |
إنهما متكاملان وليسا متنافسين. وعادةً ما تحتفظ الفرق بـ TDD لتصميم الوحدات، أضف سيناريوهات BDD لوصف السلوك الذي يوافق عليه أصحاب المصلحة فعليًا. وبما أن نقاط نهاية REST تقع تحديدًا في طبقة القبول هذه، فهي مناسبة تمامًا لـ BDD.
ما هو اختبار REST API؟
مع ازدياد شعبية أسلوب REST في بناء واجهات برمجة التطبيقات (APIs)، أصبحت أتمتة حالات اختبار واجهات برمجة تطبيقات REST جنبًا إلى جنب مع حالات اختبار واجهة المستخدم بنفس القدر من الأهمية. اختبار API يتضمن اختبار إجراءات CRUD (إنشاء-قراءة-تحديث-حذف) باستخدام طرق POST و GET و PUT و DELETE على التوالي.
ما هو التصرف؟
التصرف هو واحد من شعبية Python أطر اختبار BDD. إليك كيفية عمل Behave:
- يقوم محلل الأعمال أو الراعي أو أي شخص مسؤول عن سيناريوهات السلوك بكتابة ملفات الميزات. يستخدم ملف الميزات لغة طبيعية لوصف ميزة، أو جزء منها، مع أمثلة توضيحية للنتائج المتوقعة.
- يتم ربط خطوات هذا السيناريو بتنفيذات الخطوات المكتوبة بلغة Python.
- اختيارياً، يمكن تشغيل التعليمات البرمجية لعناصر التحكم البيئية قبل وبعد الخطوات أو السيناريوهات أو الميزات أو التشغيل بأكمله.
بعد أن اتضحت أدوار كل ملف، يمكن الآن تثبيت الإطار.
إعداد إطار عمل اختبار BDD Windows
تركيب:
- تحميل وتثبيت Python 3 من https://www.python.org/
- قم بتنفيذ الأمر التالي في موجه الأوامر لتثبيت Behave
pip install behave- SDI: PyCharm يتم استخدام إصدار المجتمع هنا — https://www.jetbrains.com/pycharm/download/
إعداد مشروع:
- قم بإنشاء مشروع جديد
- أنشئ بنية الدليل التالية
تُظهر لقطة الشاشة أعلاه التخطيط الذي يتوقعه برنامج Behave: أ ملامح دليل يحتوي على ملفات الميزات، وملف متداخل سلم دليل يحتوي على Python التطبيقات. يكتشف Behave كلا المجلدين بالاسم، لذا يجب أن تتطابق أسماء المجلدات تمامًا.
ملفات الميزة:
والآن لنقم بإنشاء ملف الميزات Sample_REST_API_Testing.feature، مع ميزة عمليات CRUD على خدمة "المنشورات".
يستخدم هذا المثال الامتداد https://jsonplaceholder.typicode.com/ خدمة REST التجريبية للمنشورات، وهي واجهة برمجة تطبيقات وهمية مجانية تقبل طلبات الكتابة وتعيد استجابات واقعية دون حفظ التغييرات.
مثال على سيناريو ما بعد
Scenario: POST post example -> creating a new post item using the 'posts' service Given I set post posts API endpoint -> prerequisite: sets the URL of the posts service When I set HEADER param request content type as "application/json" And set request body And send POST HTTP request -> the actual test step Then I receive valid HTTP response code 201 And Response body "POST" is non-empty -> verification of the response body
وبالمثل، يمكنك كتابة السيناريوهات المتبقية على النحو التالي:
Sample_REST_API_Testing.feature
Feature: Test CRUD methods in Sample REST API testing framework Background: Given I set sample REST API url Scenario: POST post example Given I Set POST posts api endpoint When I Set HEADER param request content type as "application/json" And Set request Body And Send a POST HTTP request Then I receive valid HTTP response code 201 And Response BODY "POST" is non-empty Scenario: GET posts example Given I Set GET posts api endpoint "1" When I Set HEADER param request content type as "application/json" And Send GET HTTP request Then I receive valid HTTP response code 200 for "GET" And Response BODY "GET" is non-empty Scenario: UPDATE posts example Given I Set PUT posts api endpoint for "1" When I Set Update request Body And Send PUT HTTP request Then I receive valid HTTP response code 200 for "PUT" And Response BODY "PUT" is non-empty Scenario: DELETE posts example Given I Set DELETE posts api endpoint for "1" When I Send DELETE HTTP request Then I receive valid HTTP response code 200 for "DELETE"
خطوات التنفيذ
أما بالنسبة لخطوات الميزات المستخدمة في السيناريوهات أعلاه، فيمكنك كتابة التطبيقات في Python الملفات الموجودة داخل مجلد "الخطوات".
يحدد إطار عمل Behave وظيفة الخطوة من خلال مطابقة الديكورات مع شرط ملف الميزة. على سبيل المثال، أ معطى يبحث الشرط في سيناريو ملف الميزات عن دالة خطوة تحمل @given المُزخرف. يحدث التطابق نفسه لـ متى و ثمفي حالة كلمتي "لكن" و"و"، تأخذ دالة الخطوة نفس المُزخرف المستخدم في الخطوة السابقة. على سبيل المثال، إذا جاءت كلمة "و" بعد كلمة "معطى"، فإن مُزخرف دالة الخطوة المطابق هو @given.
على سبيل المثال، يمكن تنفيذ خطوة "عندما" لطلب POST كما يلي. لاحظ كيف "application/json" يتم تمريرها من ملف الميزة إلى العنصر النائب بين قوسين - وهذا ما يسمى بالمعلمات.
# Decorator: the braced name captures a value from the feature file @when(u'I Set HEADER param request content type as "{header_content_type}"') def step_impl(context, header_content_type): # Step implementation: set the content type on the request header request_headers['Content-Type'] = header_content_type
وبالمثل، فإن تنفيذ الخطوات الأخرى في الخطوة Python سيبدو الملف كما يلي:
Sample_step_implementation.py
from behave import given, when, then, step import requests api_endpoints = {} request_headers = {} response_codes = {} response_texts = {} request_bodies = {} api_url = None @given(u'I set sample REST API url') def step_impl(context): global api_url api_url = 'https://jsonplaceholder.typicode.com' # START POST Scenario @given(u'I Set POST posts api endpoint') def step_impl(context): api_endpoints['POST_URL'] = api_url + '/posts' print('url :' + api_endpoints['POST_URL']) @when(u'I Set HEADER param request content type as "{header_content_type}"') def step_impl(context, header_content_type): request_headers['Content-Type'] = header_content_type # "And" or "But" steps are renamed by behave to match their preceding step @when(u'Set request Body') def step_impl(context): request_bodies['POST'] = {"title": "foo", "body": "bar", "userId": "1"} @when(u'Send POST HTTP request') def step_impl(context): # send the request and save the response object response = requests.post(url=api_endpoints['POST_URL'], json=request_bodies['POST'], headers=request_headers) response_texts['POST'] = response.text print("post response :" + response.text) response_codes['POST'] = response.status_code @then(u'I receive valid HTTP response code 201') def step_impl(context): print('Post rep code ;' + str(response_codes['POST'])) assert response_codes['POST'] == 201 # END POST Scenario # START GET Scenario @given(u'I Set GET posts api endpoint "{id}"') def step_impl(context, id): api_endpoints['GET_URL'] = api_url + '/posts/' + id print('url :' + api_endpoints['GET_URL']) @when(u'Send GET HTTP request') def step_impl(context): response = requests.get(url=api_endpoints['GET_URL'], headers=request_headers) response_texts['GET'] = response.text response_codes['GET'] = response.status_code @then(u'I receive valid HTTP response code 200 for "{request_name}"') def step_impl(context, request_name): print('Get rep code for ' + request_name + ':' + str(response_codes[request_name])) assert response_codes[request_name] == 200 @then(u'Response BODY "{request_name}" is non-empty') def step_impl(context, request_name): print('request_name: ' + request_name) print(response_texts) assert response_texts[request_name] is not None # END GET Scenario # START PUT/UPDATE @given(u'I Set PUT posts api endpoint for "{id}"') def step_impl(context, id): api_endpoints['PUT_URL'] = api_url + '/posts/' + id print('url :' + api_endpoints['PUT_URL']) @when(u'I Set Update request Body') def step_impl(context): request_bodies['PUT'] = {"title": "foo", "body": "bar", "userId": "1", "id": "1"} @when(u'Send PUT HTTP request') def step_impl(context): response = requests.put(url=api_endpoints['PUT_URL'], json=request_bodies['PUT'], headers=request_headers) response_texts['PUT'] = response.text print("update response :" + response.text) response_codes['PUT'] = response.status_code # END PUT/UPDATE # START DELETE @given(u'I Set DELETE posts api endpoint for "{id}"') def step_impl(context, id): api_endpoints['DELETE_URL'] = api_url + '/posts/' + id print('url :' + api_endpoints['DELETE_URL']) @when(u'I Send DELETE HTTP request') def step_impl(context): response = requests.delete(url=api_endpoints['DELETE_URL']) response_texts['DELETE'] = response.text print("DELETE response :" + response.text) response_codes['DELETE'] = response.status_code # END DELETE
⚠️ ملاحظة: مقارنة رموز الحالة مع == بدلا من is الأمور. is يختبر المعامل هوية الكائن، وليس مساواته، و Python 3.8 وما بعدها ترفع تحذير في بناء الجملة: استخدام "is" مع حرفي بالنسبة لهذا النمط. أي تأكيد مكتوب على النحو التالي: assert code is 201 ينبغي إعادة كتابتها على النحو التالي assert code == 201.
تشغيل الاختبارات
اكتمل تطوير نص الاختبار، فلنبدأ بتشغيل الاختبارات. نفّذ الأمر التالي في موجه الأوامر لتشغيل ملف الميزة:
:: Run a single feature file with the pretty formatter behave -f pretty features\feature_files_folder\Sample_REST_API_Testing.feature :: Run every feature in the project behave
يعرض هذا نتائج تنفيذ الاختبار على النحو التالي:
عرض التقرير على وحدة التحكم
يُعد إخراج وحدة التحكم مناسبًا أثناء التطوير، لكن أصحاب المصلحة عادةً ما يفضلون تقريرًا قابلاً للقراءة، وهو ما يوفره برنامج Allure.
التقارير
أولاً، قم بتثبيت برنامج تنسيق Allure Behave وأداة سطر أوامر Allure. برنامج التنسيق هو... Python الحزمة؛ أداة سطر الأوامر هي تنزيل منفصل موصوف في وثائق تقرير ألور.
:: 1. Install the formatter pip install allure-behave :: 2. Run the tests and write raw results to a folder behave -f allure_behave.formatter:AllureFormatter -o reports/allure-results features/ :: 3. Render and open the HTML report allure serve reports/allure-results
ينتج عن ذلك تقرير نتائج الاختبار بتنسيق واضح وغني بالمعلومات على النحو التالي:
تقرير الاختبار بتنسيق HTML
تقرير اختبار يعرض نتيجة السيناريو الفردية
لا تكون مجموعة البرامج العاملة مفيدة إلا إذا ظلت قابلة للقراءة مع نمو واجهة برمجة التطبيقات، وهذا هو المكان الذي تؤتي فيه الانضباطية في ملفات الميزات ثمارها.
ممارسات لكتابة ملفات ميزات Behave
تتدهور مجموعة Behave بسرعة عندما تصف السيناريوهات النقرات والبيانات المُرسلة بدلاً من السلوك. تحافظ الممارسات التالية على سهولة قراءة ملفات الميزات لأصحاب المصلحة في العمل مع الحفاظ علىping القادم Python طبقة قابلة للصيانة من قبل المهندسين.
- صف السلوك، وليس التنفيذ. اكتب "بافتراض أن لدى العميل رصيدًا كافيًا"، وليس "بافتراض أن قيمة الرصيد تساوي 500". يوضح ملف الميزة ما يفعله النظام؛ بينما يوضح ملف الخطوة كيفية التحقق من ذلك.
- احتفظ بسلوك واحد لكل سيناريو. السيناريو الذي يُؤكد رمز الحالة، ونص الاستجابة، وصف قاعدة البيانات، هو في الواقع ثلاثة سيناريوهات. فصلها يُساعد في تحديد سبب الفشل بشكل مباشر.
- انقل الإعدادات المشتركة إلى الخلفية. يستخدم المثال أعلاه
Background: Given I set sample REST API urlبحيث يرث كل سيناريو الأساس URL دون تكرار ذلك. - استخدم المعاملات بدلاً من التكرار. أماكن وضع الأشياء المدعمة مثل
"{request_name}"لنفترض أن دالة خطوة واحدة تخدم تأكيدات GET و PUT و DELETE، ولهذا السبب يحتاج المثال إلى تعريفات خطوات أقل بكثير من السيناريوهات. - استخدم مخطط السيناريو لبيانات التباين. عندما يتعين إثبات السلوك نفسه لعدة مدخلات، فإن
Examples:الجدول أوضح من السيناريوهات المنسوخة. - تجنب الاعتماد المتبادل بين السيناريوهات. لا يضمن Behave تشغيل سيناريو POST قبل سيناريو GET في جميع الإعدادات. يجب أن يُنشئ كل سيناريو الحالة التي يحتاجها.
- استبدل المتغيرات العامة على مستوى الوحدة بالسياق. يخزن المثال نقاط النهاية والاستجابات في قواميس الوحدات النمطية للاختصار. في بيئات الإنتاج، يتم تخزينها على Behave's
contextالكائن بحيث تتم إعادة ضبط الحالة بسلاسة بين السيناريوهات. - قم بتمييز السيناريوهات لتشغيلات انتقائية. علامات مثل
@smokeor@regressionالسماحbehave --tags=@smokeمما يحافظ على سرعة استجابة خط الأنابيب.
إن تطبيق هذه العادات من ملف الميزات الأول يحافظ على قيمة مجموعة أدوات تطوير البرمجيات القائمة على السلوك (BDD) لفترة طويلة تتجاوز أمثلة CRUD الأولية. وغالبًا ما تقترن الفرق التي توسع هذا النهج بـ Cucumber لمشاريع JVM أو Postman لإجراء عمليات فحص استكشافية لواجهة برمجة التطبيقات (API).






