Given-When-Then هو نمط هيكلي لوصف سيناريوهات الاختبار، استعارته BDD من domain-driven design وتم تكييفه لـ Behaviour-Driven Development. يقسم التنسيق السيناريو إلى ثلاثة أجزاء منطقية: الشروط المسبقة (Given)، والإجراء (When)، والنتيجة المتوقعة (Then). وفقًا لـ Martin Fowler (2023)، فإن Given-When-Then ليس مجرد تنسيق للاختبارات، بل أداة تفكير تنظم تحليل المتطلبات وتصميم السيناريوهات قبل بدء التنفيذ.
أهم النقاط
Given-When-Then هو نمط لوصف السلوك، صاغه Dan North لأول مرة في عام 2006 كجزء من منهجية Behavior-Driven Development. يحل النمط مشكلة الأوصاف غير المنظمة لسيناريوهات الاختبار، والتي غالبًا ما تحتوي على مزيج من الشروط المسبقة والإجراءات والتحققات بترتيب عشوائي.
الفكرة الرئيسية للنمط هي فصل المسؤوليات بين ثلاث كتل. كل كتل مسؤولة بالضبط عن جانب واحد من السيناريو: الحالة قبل، والحدث أثناء، والتحقق بعد. هذا يجعل السيناريو قابلاً للقراءة والتحقق والأتمتة. وفقًا لدراسة أجراها مطورو إطار Cucumber (2024)، تتطلب السيناريوهات التي تتبع نمط Given-When-Then بدقة وقتًا أقل بنسبة 42% لفهمها من قبل عضو جديد في الفريق.
استعار Dan North فكرة الهيكل الثلاثي من صياغة الاختبارات في TDD ومنهجية Test-by-Example (التي أنشأها Brian Marick). اقترح ماريك وصف المتطلبات من خلال الأمثلة (examples) التي تعمل في الوقت نفسه كاختبارات. قام Given-When-Then بإضفاء الطابع الرسمي على هذه الفكرة، محولاً الأمثلة غير المنظمة إلى نمط قابل للتكرار.
لا يُطبق نمط Given-When-Then فقط في سيناريوهات BDD بلغة Gherkin، بل أيضًا في اختبارات الوحدة العادية باستخدام JUnit و XCTest وأطر أخرى. تُعد التعليقات في الكود التي تقسم الاختبار إلى ثلاث كتل ممارسة شائعة لتحسين قراءة قاعدة الاختبارات. توصي Google بهذا النهج في كتابها «Software Engineering at Google» (2020).
كل كتلة من Given-When-Then لها دلالات محددة بدقة وقواعد للمحتوى. يؤدي انتهاك هذه القواعد إلى سيناريوهات يصعب أتمتتها أو فهمها.
تصف كتلة Given حالة النظام قبل تنفيذ الإجراء قيد الاختبار. تتضمن: الكائنات الموجودة (مستخدم، طلب، إعدادات)، الحالات النشطة (مُصرح له، متصل بالشبكة)، والقيم الأولية للبيانات. يجب أن يكون كل Given قابلاً للتحقق — إذا كانت حالة النظام لا تتطابق مع Given، فيجب تخطي السيناريو أو تحضير بيئة الاختبار مسبقًا.
تصف كتلة When الحدث الوحيد الذي يشغل السلوك قيد الاختبار. يمكن أن يكون استدعاء دالة، أو نقرة على زر، أو استلام إشعار أو رد من الخادم. القاعدة الأساسية هي When واحد لكل سيناريو. إذا كنت بحاجة إلى التحقق من تسلسل إجراءات، فأنشئ سيناريوهات منفصلة، وليس سلسلة من When.
// Given: ننشئ بيانات الاختبار
val user = User(email = "test@example.com", balance = 500.0)
val product = Product(price = 150.0)
// When: ننفذ الإجراء
val result = PurchaseUseCase().buy(user, product)
// Then: نتحقق من النتيجة
assertEquals(PurchaseResult.Success, result)
assertEquals(350.0, user.balance)
تتحقق كتلة Then من أن النظام قد انتقل إلى الحالة المتوقعة. يشمل ذلك: القيم المُعادة، التغييرات في حالة الكائنات، استدعاءات الخدمات الخارجية (من خلال التحقق من mocks)، والتغييرات في واجهة المستخدم. يمكن أن تحتوي كل كتلة Then على عدة تحققات، ولكن جميعها تتعلق بإجراء واحد.
Given-When-Then و Arrange-Act-Assert (AAA) هما شكلان مختلفان لنفس النمط الثلاثي، لكن بجماهير مستهدفة مختلفة. فهم الاختلافات بينهما يساعد في اختيار التنسيق الصحيح لمهمة محددة.
| الجانب | Given-When-Then | Arrange-Act-Assert |
|---|---|---|
| الأصل | BDD، تحليل الأعمال | اختبارات الوحدة |
| اللغة | طبيعية (Gherkin) | كود (Kotlin, Swift, Java) |
| الجمهور | الفريق بأكمله + العميل | المطورون |
| مستوى التفصيل | عالي المستوى | تفصيلي |
| الأتمتة | Cucumber, SpecFlow | JUnit, XCTest, Mockito |
نمط Given-When-Then مثالي للسيناريوهات التي تُناقش مع العميل أو المحلل: معايير القبول للميزات، حالات الاستخدام، فحوصات الانحدار. تسمح صياغة Gherkin بكتابة هذه السيناريوهات دون معرفة بالبرمجة.
Arrange-Act-Assert هو الخيار الطبيعي لاختبارات الوحدة التي تتحقق من دالة أو فئة معينة. لا يتطلب تنسيق AAA أطرًا إضافية ويعمل في أي لغة برمجة. لتطوير iOS، توصي Apple بـ AAA في وثائق XCTest (2024).
دعنا نستعرض أمثلة عملية لـ Given-When-Then بلغة Kotlin لتطبيق Android. المثال الأول هو اختبار سلة التسوق باستخدام MockK. الثاني هو اختبار منطق الإشعارات الفورية.
class CartTest {
fun `apply discount when total exceeds threshold`() {
// Given
val cart = Cart()
cart.addItem(Item("Laptop", price = 1000.0))
cart.addItem(Item("Mouse", price = 50.0))
val discount = DiscountCalculator(0.1)
// When
val total = discount.applyIfEligible(cart)
// Then
assertEquals(945.0, total)
assertTrue("Discount was not applied", total < 1050.0)
}
}
المثال الثاني يوضح Given-When-Then مع كود غير متزامن. هنا Given يحدد حالة Firebase Cloud Messaging، When — استلام إشعار فوري، Then — التحقق من المعالجة.
class PushNotificationTest {
fun `handle push notification when app in background`() = runTest {
// Given
val prefs = mockk<SharedPreferences>()
every { prefs.getString("token", null) } returns "fcm-token-abc"
val handler = PushHandler(prefs)
// When
val data = RemoteMessage().apply {
putData("type", "order_update")
putData("order_id", "123")
}
val result = handler.handleNotification(data)
// Then
assertEquals(NotificationAction.OpenOrder("123"), result)
}
}
المثال الثالث هو سيناريو BDD بلغة Gherkin يوضح Given-When-Then في سياق اختبارات القبول:
Feature: User Authorization
Scenario: User cannot login with expired token
Given the user has an expired refresh token
When they try to access the protected profile screen
Then they should see the login screen
And the app should clear all cached data
يتطلب التطبيق الفعال لـ Given-When-Then اتباع عدة ممارسات مثبتة. تضمن هذه الممارسات قابلية القراءة والصيانة والأتمتة للسيناريوهات.
قاعدة صارمة: سيناريو واحد — إجراء واحد. إذا كنت بحاجة إلى التحقق من تسلسل عدة When، فأنشئ عدة سيناريوهات حيث تصبح نتيجة السابق شرطًا مسبقًا للتالي. هذا يجعل السيناريو ذريًا ومفهومًا.
يجب أن يصف Given الجوهر، وليس الأرقام المحددة. بدلاً من «Given المستخدم إيفانوف برصيد 500 روبل» — «Given مستخدم برصيد كافٍ». تُنقل البيانات المحددة إلى Scenario Outline مع جدول Examples. هذا يجعل السيناريو عالميًا وقابلًا لإعادة الاستخدام.
دمج سيناريوهات Given-When-Then في خط أنابيب التكامل المستمر يحولها من توثيق إلى حماية ضد الانحدار. كل merge request في مشروع جوال يشغل تلقائيًا سيناريوهات BDD ويمنع الدمج إذا فشل سيناريو واحد على الأقل.
تُشغل سيناريوهات BDD باستخدام Cucumber لنظام Android من خلال مهمة Gradle ./gradlew cucumber. لنظام iOS (Quick/Nimble) — من خلال xcodebuild test. في أنظمة CI (GitHub Actions, GitLab CI, Bitrise)، تُنفذ اختبارات BDD على المحاكيات أو الأجهزة الحقيقية. يُنشأ التقرير بتنسيق HTML مفهوم للمديرين: سيناريوهات خضراء — ناجحة، حمراء — فشل مع ذكر الخطوة.
تُخزن ملفات .feature في المستودع بجانب الكود وتخضع لـ مراجعة الكود. ينشئ المحلل merge request مع سيناريوهات جديدة قبل بدء التطوير (BDD-first). يكتب المطور تعريفات الخطوات والتنفيذ لتصبح هذه السيناريوهات خضراء. عندما تمر جميع السيناريوهات — تكون الوظيفة جاهزة. هذا النهج، الموضح في كتاب Gojko Adzic «Specification by Example» (2011)، يحول المتطلبات إلى أثر قابل للتنفيذ.
الأسئلة الشائعة
من الناحية الهيكلية، نعم، إنه نفس النمط الثلاثي. الفرق في الجمهور: Given-When-Then موجه للغة الأعمال ويُستخدم في BDD مع Gherkin، بينما Arrange-Act-Assert هو تنسيق تقني لاختبارات الوحدة. يعتمد الاختيار على السياق والفريق.
لا يوجد حد، لكن يُوصى بما لا يزيد عن 3–5 تحققات لكل Then. إذا كان هناك المزيد من التحققات، فمن المحتمل أن السيناريو يتحقق من الكثير في إجراء واحد. قسمه إلى عدة سيناريوهات مع Then مختلفة.
لا. يمكن استخدام النمط في أي إطار اختبار، ببساطة عن طريق تقسيم الاختبار بتعليقات أو أسطر فارغة إلى ثلاث كتل. Gherkin ضروري فقط إذا كانت السيناريوهات تُكتب بتنسيق .feature لـ Cucumber أو SpecFlow.
يُوصى باستخراج الشروط المسبقة المتكررة إلى Background (Gherkin) أو طرق @Before (JUnit). إذا كانت الشروط المسبقة معقدة، استخدم نمط Builder لإنشاء بيانات الاختبار. هذا يحافظ على Given قصيرًا وقابلاً للقراءة.
لا. When هي كتلة إجبارية تصف الإجراء. إذا كان السيناريو يتحقق فقط من حالة بدون إجراء (على سبيل المثال، «عند تحميل التطبيق، يجب تخزين البيانات في ذاكرة التخزين المؤقت»)، فإن When تصف المُشغّل: «عندما يبدأ التطبيق».
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.