Test-Driven Development (TDD) هي منهجية تطوير يتم فيها كتابة الاختبارات قبل تنفيذ الكود. يقوم المطور أولاً بصياغة السلوك المتوقع في شكل اختبار فاشل، ثم يكتب الكود الأدنى لاجتيازه، وبعد ذلك يعيد هيكلة النتيجة. وفقًا لـ Martin Fowler (2023)، TDD ليست تقنية اختبار — إنها تقنية تصميم تضبط الهندسة المعمارية وتقلل من عدد العيوب في مرحلة كتابة الكود.
الرئيسية
التطوير القائم على الاختبار هو ممارسة تطوير برمجيات حيث تحدد الاختبارات الآلية كتابة كود الإنتاج. على عكس النهج التقليدي حيث يتم كتابة الكود ثم اختباره، يعكس TDD التسلسل: أولاً يُكتب الاختبار، ثم الكود الذي يجتاز هذا الاختبار.
يعتبر Kent Beck مؤسس TDD، حيث صاغ هذه الممارسة في أواخر التسعينيات كجزء من منهجية Extreme Programming (XP). في كتاب «Test-Driven Development: By Example» (2002)، وصف بيك خمس قواعد لـ TDD أصبحت قانونية: اكتب الاختبار قبل كود الإنتاج، اكتب فقط القدر الكافي من الكود لاجتياز الاختبار، وأعد الهيكلة بعد كل دورة.
المبدأ الأول — الاختبار يحدد الواجهة. يُجبر المطور على التفكير في كيفية استخدام المكون قبل التفكير في كيفية تنفيذه. هذا يشكل واجهة برمجة تطبيقات نظيفة منذ البداية.
المبدأ الثاني — التنفيذ الأدنى. عندما يُكتب الاختبار، يكتب المطور بالضبط مقدار كود الإنتاج اللازم لاجتيازه — ولا سطر أكثر. هذا يمنع التجريد المبكر والتعقيد المفرط، الذي يسميه Martin Fowler Speculative Generality.
الفرق الرئيسي بين TDD والاختبار اللاحق — انضباط التسلسل. في TDD، لا يتحقق الاختبار من الكود فقط — بل يوجه هيكله. وفقًا لدراسة Microsoft Research (Nagappan et al., 2008)، تظهر الفرق التي تطبق TDD انخفاضاً في كثافة العيوب بنسبة 40–90% مقارنة بالفرق التي تستخدم النهج التقليدي.
دورة Red-Green-Refactor هي تسلسل من ثلاث خطوات يتكرر لكل اختبار جديد. Red: كتابة اختبار لا يجتاز. Green: كتابة الكود الأدنى لاجتياز الاختبار. Refactor: تحسين الكود دون تغيير سلوكه.
يكتب المطور اختباراً يتحقق من وظائف غير منفذة بعد. في هذه المرحلة، يجب أن يفشل الاختبار — وهذا يؤكد أن الاختبار يتحقق بالفعل من شيء ما. في بيئة تطوير Android، يعرض إطار JUnit 5 مؤشراً أحمر للاختبارات الفاشلة، مما أعطى الاسم لهذه المرحلة.
class CalculatorTest {
fun testAddition() {
val result = Calculator().add(2, 3)
Assertions.assertEquals(5, result)
}
}
في هذه المرحلة، يُكتب الحد الأدنى من كود الإنتاج الكافي لاجتياز الاختبار. لا زيادة — فقط ما هو مطلوب للمؤشر الأخضر. إذا كان يمكن أن يكون التنفيذ ثابتاً، فليكن ثابتاً. ستحدث إعادة الهيكلة في الخطوة التالية عند ظهور اختبارات جديدة.
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
}
الاختبار الأخضر هو تأمين لـ إعادة الهيكلة. يمكن للمطور إعادة كتابة التنفيذ، أو تحسين الأداء، أو تحسين readability، مع الثقة بأن الاختبار سيكتشف فوراً أي انحراف عن السلوك المتوقع. في تطوير Android المحمول، هذه المرحلة مهمة بشكل خاص لاستخراج الواجهات المشتركة وتقليل ازدواجية الكود.
تطبيق TDD في المشاريع المحمولة يعطي فوائد قابلة للقياس، مؤكدة من قبل الأبحاث الأكاديمية وممارسات استوديوهات التطوير الرائدة.
أظهرت دراسة IBM (Bhat & Nagappan, 2006) على أربعة مشاريع صناعية أن الفرق التي تستخدم TDD ترتكب عيوباً أقل بنسبة 40% مقارنة بالفرق المماثلة التي تعمل بالنهج التقليدي. للتطوير المحمول، حيث تكلفة إصلاح الخلل بعد إصدار Google Play أعلى بكثير مما هي عليه في مرحلة كتابة الكود، هذا المقياس حاسم.
الاختبارات المكتوبة بـ TDD تعمل كـ توثيق حي لواجهة API. يمكن للمطور الذي ينضم إلى المشروع قراءة الاختبارات وفهم كيفية استخدام كل مكون. هذا ذو قيمة خاصة في ظروف دوران الفريق العالي — وهي مشكلة نموذجية لاستوديوهات التطوير المحمول.
تغطية الكود التي تتجاوز 90% تسمح للمطورين بإعادة الهيكلة دون خوف من كسر شيء ما. Google، في كتابها «Software Engineering at Google» (2020)، تسمي تغطية الاختبار عاملاً رئيسياً يسمح بالحفاظ على قاعدة الكود نظيفة في المشاريع التي تحتوي على ملايين الأسطر من الكود.
يشمل نظام TDD البيئي في التطوير المحمول أدوات لاختبارات الوحدة، وإنشاء الكائنات الوهمية (mocking)، والتحقق من مكونات واجهة المستخدم — لكل من Android و iOS.
| الأداة | المنصة | الغرض |
|---|---|---|
| JUnit 5 | Android (Kotlin/Java) | الإطار الأساسي لاختبارات الوحدة |
| Mockito | Android | إنشاء كائنات mock والتحقق من الاستدعاءات |
| MockK | Android (Kotlin) | Mocking ببناء جملة Kotlin-first ودعم coroutines |
| Turbine | Android | اختبار Kotlin Flow والتيارات التفاعلية |
| XCTest | iOS (Swift) | إطار الاختبار القياسي |
لمشاريع Android بلغة Kotlin، يتضمن الرصيف القياسي JUnit 5 + MockK. MockK أفضل من Mockito لأنه يدعم ميزات Kotlin من الدرجة الأولى — sealed class و coroutines و suspend functions — بدون إعدادات إضافية.
في تطوير iOS، يتم تطبيق TDD من خلال XCTest — إطار Apple المدمج الذي يوفر التأكيدات وفئات الاختبار والتكامل مع CI/CD عبر Xcode Server أو GitHub Actions. للـ mocking في iOS، تُستخدم مكتبات Cuckoo و OHHTTPStubs.
لننظر في سيناريو حقيقي لـ TDD بلغة Kotlin لنظام Android — اختبار مستودع المستخدمين. أولاً نكتب الاختبار، ثم التنفيذ الذي يجتاز هذا الاختبار.
class UserRepositoryTest {
private val api = mockk<UserApi>()
private val dao = mockk<UserDao>()
private val repo = UserRepository(api, dao)
fun `when api returns user then cache and emit`() = runTest {
val user = User(1, "Alice")
coEvery { api.getUser(1) } returns user
every { dao.insert(user) } returns Unit
val result = repo.getUser(1)
assertEquals(user, result)
verify { dao.insert(user) }
}
}
class UserRepository(
private val api: UserApi,
private val dao: UserDao
) {
suspend fun getUser(id: Int): User {
val user = api.getUser(id)
dao.insert(user)
return user
}
}
بعد اجتياز الاختبار الأول، نضيف اختباراً ثانياً — نتحقق من السلوك عند خطأ الشبكة. الآن يحدد الاختبار أنه عند فشل API، يجب أن يعيد المستودع البيانات من ذاكرة التخزين المؤقت.
fun `when api fails then return cached user`() = runTest {
val cached = User(1, "Cached Alice")
coEvery { api.getUser(1) } throws IOException()
every { dao.getById(1) } returns cached
val result = repo.getUser(1)
assertEquals(cached, result)
}
الانتقال إلى TDD يرتبط بأخطاء نموذجية يمكن أن تلغي جميع فوائد المنهجية. فهم هذه المزالق يساعد الفرق على تطبيق الممارسة بشكل أكثر فعالية.
أول وأكثر أنماط مكافحة شيوعاً — اختبار حجم كبير جداً من الوظائف في اختبار واحد. يجب أن يتحقق الاختبار من تأكيد واحد بالضبط. إذا فشل الاختبار، يجب أن يعرف المطور بالضبط ما الذي تعطل دون تصحيح إضافي.
الخطأ الثاني — كتابة اختبار ينجح من البداية. إذا لم يكن الاختبار أحمر مرة واحدة على الأقل، فليس هناك ثقة من أنه يتحقق بالفعل من شيء ما. القاعدة: لا تثق أبداً في اختبار لم تره فاشلاً.
الخطأ النموذجي الثالث — التوقف عند المرحلة الخضراء. إعادة الهيكلة ليست اختيارية بل خطوة إلزامية في الدورة. بدونها، تتدهور قاعدة الكود، وتصبح الاختبارات هشة، وتفقد فوائد TDD.
الأسئلة الشائعة
TDD هي في المقام الأول تقنية تصميم، وليست تقنية اختبار. تلعب الاختبارات في TDD دور المواصفات: فهي تحدد API المكون قبل تنفيذه. Kent Beck نفسه يسمي TDD «انضباط التصميم، وليس الاختبار».
وفقاً لدراسات Microsoft Research، تحتاج الفرق من 3 إلى 6 أشهر من الممارسة المستمرة حتى يصبح TDD عادة. في أول 2–3 أسابيع، تنخفض الإنتاجية بنسبة 15–30%، ولكن بعد التكيف تعود إلى المستوى الأصلي أو تتجاوزه بفضل تقليل وقت التصحيح.
نعم، ولكن مع قيود. لـ منطق واجهة المستخدم (ViewModel, State)، TDD قابل للتطبيق مباشرة. للمكونات البصرية (Compose UI, SwiftUI Views)، اختبارات اللقطات (snapshot testing) تكمل TDD ولكنها لا تحل محله. يُوصى بفصل منطق الأعمال عن العرض.
لـ الكود القديم، يُوصى باستراتيجية «اختبارات التوصيف» (characterization tests) — حيث تُكتب الاختبارات على السلوك الحالي، ثم يُعاد هيكلة الكود. هذا النهج موصوف في كتاب Michael Feathers «Working Effectively with Legacy Code» (2004) ويسمح بتطبيق TDD تدريجياً.
TDD و Clean Architecture يعززان بعضهما البعض. تتطلب الهندسة النظيفة حدوداً واضحة بين الطبقات، و TDD يُجبر المطور على تصميم هذه الحدود من خلال الاختبارات. يتم اختبار طبقة المجال بشكل منعزل مع تبعيات mock، وطبقة البيانات — من خلال اختبارات التكامل.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا