ما هو اختبار BDD؟ إطار عمل تطوير قائم على السلوك

⚡ ملخص ذكي

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

  • 🇧🇷 المبدأ الأساسي: يوسع تطوير البرمجيات الموجه بالسلوك نطاق تطوير البرمجيات الموجه بالاختبار من خلال التعبير عن كل سيناريو بلغة طبيعية يمكن لغير المبرمجين قراءتها والموافقة عليها.
  • 🧱 هيكل السيناريو: يحدد Given الشروط المسبقة، وينفذ When الإجراء، ويؤكد Then النتيجة، بينما يرث And أو But الديكور الخاص بالخطوة السابقة.
  • 🌐 تغطية REST: يُظهر المثال سلوك CRUD الكامل من خلال POST و GET و PUT و DELETE على خدمة منشورات jsonplaceholder العامة.
  • 🐍 إعداد الإطار: يتم تثبيت Behave من خلال pip على Python 3 ويتوقع وجود دليل للميزات يحتوي على ملفات الميزات بالإضافة إلى حزمة خطوات تحتوي على التطبيقات.
  • 🔗 ربط الخطوات: تُطابق عناصر التزيين مثل @given و@when و@then نص الميزة، بينما تُمرر العناصر النائبة بين قوسين القيم من السيناريو إلى Python وظيفة.
  • ▶ ️ التنفيذ: يقوم برنامج التنسيق الجميل بطباعة نتيجة النجاح أو الفشل المرمزة بالألوان لكل خطوة في وحدة التحكم.
  • 📊 التقرير: يقوم برنامج التنسيق allure-behave بكتابة نتائج قابلة للقراءة آلياً، والتي يقوم سطر أوامر Allure بعرضها كتقرير HTML قابل للتصفح.

ما هو اختبار BDD؟

ما هو اختبار BDD (التطوير الموجه بالسلوك)؟

اختبار BDD (التطوير المبني على السلوك). هي تقنية من تقنيات تطوير البرمجيات الرشيقة، وهي امتداد لتقنية تطوير البرمجيات الموجهة بالاختبار (TDD). في BDD، تُكتب حالات الاختبار بلغة طبيعية يمكن حتى لغير المبرمجين قراءتها.

كيف يعمل اختبار BDD؟

لنفترض أنك مكلف بإنشاء وحدة تحويل الأموال في تطبيق الخدمات المصرفية عبر الإنترنت.

هناك طرق متعددة لاختباره:

  1. يجب إجراء تحويل الأموال إذا كان هناك رصيد كافٍ في الحساب المصدر
  2. يجب أن يتم تحويل الأموال إذا كانت تفاصيل الحساب الوجهة صحيحة
  3. يجب أن تتم عملية تحويل الأموال إذا كانت كلمة مرور المعاملة / رمز RSA / بيانات المصادقة الأمنية التي أدخلها المستخدم صحيحة
  4. ينبغي إجراء تحويل الأموال حتى في أيام العطلات الرسمية.
  5. يجب أن يتم تحويل الأموال في تاريخ مستقبلي يحدده صاحب الحساب

استخدم سيناريو الاختبار يصبح الأمر أكثر تعقيدًا وتفصيلًا عند النظر في ميزات إضافية مثل تحويل مبلغ 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

تركيب:

إعداد مشروع:

  • قم بإنشاء مشروع جديد
  • أنشئ بنية الدليل التالية

إعداد مشروع

تُظهر لقطة الشاشة أعلاه التخطيط الذي يتوقعه برنامج 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

تقرير الاختبار بتنسيق HTML

تقرير اختبار يعرض نتيجة السيناريو الفردية

لا تكون مجموعة البرامج العاملة مفيدة إلا إذا ظلت قابلة للقراءة مع نمو واجهة برمجة التطبيقات، وهذا هو المكان الذي تؤتي فيه الانضباطية في ملفات الميزات ثمارها.

ممارسات لكتابة ملفات ميزات Behave

تتدهور مجموعة Behave بسرعة عندما تصف السيناريوهات النقرات والبيانات المُرسلة بدلاً من السلوك. تحافظ الممارسات التالية على سهولة قراءة ملفات الميزات لأصحاب المصلحة في العمل مع الحفاظ علىping القادم Python طبقة قابلة للصيانة من قبل المهندسين.

  1. صف السلوك، وليس التنفيذ. اكتب "بافتراض أن لدى العميل رصيدًا كافيًا"، وليس "بافتراض أن قيمة الرصيد تساوي 500". يوضح ملف الميزة ما يفعله النظام؛ بينما يوضح ملف الخطوة كيفية التحقق من ذلك.
  2. احتفظ بسلوك واحد لكل سيناريو. السيناريو الذي يُؤكد رمز الحالة، ونص الاستجابة، وصف قاعدة البيانات، هو في الواقع ثلاثة سيناريوهات. فصلها يُساعد في تحديد سبب الفشل بشكل مباشر.
  3. انقل الإعدادات المشتركة إلى الخلفية. يستخدم المثال أعلاه Background: Given I set sample REST API url بحيث يرث كل سيناريو الأساس URL دون تكرار ذلك.
  4. استخدم المعاملات بدلاً من التكرار. أماكن وضع الأشياء المدعمة مثل "{request_name}" لنفترض أن دالة خطوة واحدة تخدم تأكيدات GET و PUT و DELETE، ولهذا السبب يحتاج المثال إلى تعريفات خطوات أقل بكثير من السيناريوهات.
  5. استخدم مخطط السيناريو لبيانات التباين. عندما يتعين إثبات السلوك نفسه لعدة مدخلات، فإن Examples: الجدول أوضح من السيناريوهات المنسوخة.
  6. تجنب الاعتماد المتبادل بين السيناريوهات. لا يضمن Behave تشغيل سيناريو POST قبل سيناريو GET في جميع الإعدادات. يجب أن يُنشئ كل سيناريو الحالة التي يحتاجها.
  7. استبدل المتغيرات العامة على مستوى الوحدة بالسياق. يخزن المثال نقاط النهاية والاستجابات في قواميس الوحدات النمطية للاختصار. في بيئات الإنتاج، يتم تخزينها على Behave's context الكائن بحيث تتم إعادة ضبط الحالة بسلاسة بين السيناريوهات.
  8. قم بتمييز السيناريوهات لتشغيلات انتقائية. علامات مثل @smoke or @regression السماح behave --tags=@smokeمما يحافظ على سرعة استجابة خط الأنابيب.

إن تطبيق هذه العادات من ملف الميزات الأول يحافظ على قيمة مجموعة أدوات تطوير البرمجيات القائمة على السلوك (BDD) لفترة طويلة تتجاوز أمثلة CRUD الأولية. وغالبًا ما تقترن الفرق التي توسع هذا النهج بـ Cucumber لمشاريع JVM أو Postman لإجراء عمليات فحص استكشافية لواجهة برمجة التطبيقات (API).

الأسئلة الشائعة

كلاهما يقرأ ملفات ميزات Gherkin. Cucumber يربط الخطوات بـ Javaروبي، أو Javaبينما يربطهم السلوك بـ Script Pythonاختر الخيار الذي يتوافق مع مجموعة أدوات التطبيق الخاصة بك حتى تتمكن تعريفات الخطوات من إعادة استخدام أدوات الاختبار الموجودة.

نعم. يُعيد برنامج Behave رمز خروج غير صفري عند فشل أي سيناريو، لذا Jenkinsقد تفشل عملية البناء تلقائيًا عند استخدام GitLab CI أو GitHub Actions. انشر مجلد نتائج Allure كملف ناتج عن عملية البناء لأغراض إعداد التقارير.

نعم. AI يقوم المساعدون بصياغة سيناريوهات "المعطيات-الحالات-النتائج" من قصص المستخدم أو مواصفات OpenAPI. Revاطلع على المخرجات الخاصة بالحالات السلبية المفقودة وقواعد العمل، لأن السيناريوهات التي تم إنشاؤها غالباً ما تغطي المسار السعيد فقط.

تكتشف أدوات الذكاء الاصطناعي السيناريوهات المكررة أو شبه المكررة، وتقترح خطوات قابلة لإعادة الاستخدام ومُعَدّة بمعايير محددة، وتُصنّف حالات الفشل غير المستقرة حسب السبب المحتمل. وهذا يقلل من جهد التدقيق اليدوي مع تزايد ملفات الميزات بين الفرق.

استرجع الرمز المميز مرة واحدة في دالة before_all في ملف environment.py، وخزّنه في كائن السياق، وأرفقه كعنوان Authorization في كل خطوة من خطوات الطلب. احتفظ ببيانات الاعتماد في متغيرات البيئة، ولا تُخزّنها أبدًا في ملفات الميزات.

تلخيص هذه التدوينة بـ: