إلى أين ينتهي unit test وأين يبدأ integration test - دليل عمليّ لرسم الحدّ الفاصل

· · Testing, Unit Testing, Integration Testing, Test Design, Windows Development, C#, .NET

من أكثر أجزاء تصميم الاختبار صعوبةً بهدوء، تحديدُ ما الذي ينبغي أن يبقى ضمن unit test وما الذي ينبغي أن يرتفع إلى integration test.

والطرفان الخطران معروفان:

  • جعل كلّ شيء unit test لأنّك تريد سرعة تشغيل المجموعة
  • جعل كلّ شيء integration test لأنّك تريد شعوراً أقرب إلى الإنتاج

الأوّل يقود إلى اختبارات مثقلة بالـ mock تُفلت الأماكن التي ينكسر فيها الإنتاج فعلاً.
والثاني يقود إلى اختبارات بطيئة وهشّة ومكلفة الصيانة.

وفي الواقع العمليّ، يصير الحدّ أوضح بكثير حين تنظر إلى هذه الأسئلة الأربعة:

  • هل نتحقّق من منطقنا الخاصّ، أم من الغراء الذي يصلنا بالعالم الخارجيّ؟
  • هل يبقى معنى الاختبار محفوظاً إذا استبدلنا الاعتماد بـ in-memory fake؟
  • هل الموضوع الحقيقيّ هو DB / الملفّات / HTTP / DI / الإعدادات / سلوك الإطار / سلوك OS؟
  • هل نحتاج إلى تشغيل تركيبات إدخال كثيرة بسرعة؟

ما إن يصير هذا الرباعيّ ظاهراً، يسهل رسم الحدّ بين unit test وintegration test كثيراً.

يستند هذا المقال إلى مادّة Microsoft Learn وMartin Fowler المتاحة حتى مارس 2026. 123

1. الجواب القصير

إن ضغطنا الجواب في صورة عمليّة، يصير كالآتي:

  1. المنطق الصافي ينتمي إلى unit test
  2. الاتّصالات والتوصيل والتحويلات والاختلافات البيئيّة تنتمي إلى integration test
  3. إن استطاعت أيّ من الطبقتين التحقّق منه، فابدأ بـ unit test
  4. ينبغي أن تكون integration tests ضيّقة وهادفة، لا واسعة وثقيلة

في جملة واحدة: unit tests اختباراتُ حُكم، وintegration tests اختباراتُ اتّصال.

أمور كحساب السعر، وانتقالات الحالة، والتحقّق من الإدخال، وقواعد الموافقة، وتصنيف الاستثناءات، يُفضَّل عادةً إبقاؤها في unit tests، لأنّها مكتملة بلا موارد خارجيّة وتسمح بتشغيل تركيبات كثيرة بسرعة.
في المقابل، تنفيذ SQL، وتسلسل JSON / CSV، وrouting، وmodel binding، وتسجيل DI، وأقفال الملفّات، والصلاحيّات، وتسجيل COM، وسلوك 32-bit / 64-bit، وسلوك STA / MTA، أمور قد تنكسر لحظة الاتّصال الفعليّ، فإبقاؤها في integration tests أأمن عادةً.

تُؤطِّر Integration tests in ASP.NET Core من Microsoft Learn integration tests بوصفها شيئاً ينبغي إبقاؤه مركَّزاً على سيناريوهات البنية التحتيّة المهمّة حين تكفي unit tests للبقيّة.

2. ما الذي أعنيه بـ unit tests وintegration tests

في هذا المقال، ينقسم الأمر هكذا:

المستوى ما الذي يتحقّق منه الإعداد النموذجيّ
Unit test صحّة مسؤوليّة واحدة معزولة قطع الموارد الخارجيّة عبر fakes / mocks / stubs
Integration test التفاعل بين مكوّنات متعدّدة، شاملاً سلوك البنية التحتيّة والإطار DB حقيقيّ، ملفّات حقيقيّة، serializer حقيقيّ، host حقيقيّ، pipeline حقيقيّ، وهكذا
E2E / functional test تدفّق المستخدم من طرف إلى طرف عبر التطبيق كلّه تطبيق منشور، خدمات متعدّدة، متصفّح حقيقيّ أو عمليّة حقيقيّة

في إرشادات أفضل ممارسات ‎.NET، يوصَف unit test الجيّد بأنّه fast / isolated / repeatable ومستقلّ عن عوامل خارجيّة كنظام الملفّات أو قاعدة البيانات. راجع Unit testing best practices for .NET لملخّص نظيف.

كذلك، لا يعني integration testing فقط “اختبارات كبيرة عابرة للعمليّات”.
فحتى داخل عمليّة واحدة، إن وصلت مكوّنات حقيقيّة متعدّدة وتحقّقت من سلوك إطار أو بنية تحتيّة، فأنت قد دخلت أرض integration test.

مثلاً، حين تُجري unit test على controller action في ASP.NET Core، تبقى عادةً ضمن منطق قرار الـ action نفسه، بينما يُترك routing وmodel binding وfilters لـ integration tests. توثّق Microsoft هذا الفصل بوضوح في Unit test controller logic in ASP.NET Core.

3. جدول قرار في صفحة واحدة

إليك أوّلاً الجدول الأكثر فائدة عمليّاً:

ما تريد التحقّق منه طبقة الاختبار الأساسيّة ملاحظات
حساب السعر، الخصومات، انتقالات الحالة، التحقّق من الإدخال Unit test تريد كثيراً من التركيبات
تصنيف الاستثناءات، اختيار رسالة الخطأ، قرار retry-or-not Unit test المعنى مكتمل بلا I/O حقيقيّ
SQL في الـ Repository / تحويل ORM، سلوك transaction Integration test يهمّ سلوك DB حقيقيّ وprovider حقيقيّ
serialize / deserialize لـ JSON / XML / CSV Integration test الكائنات المزيَّفة نادراً ما تلتقط انحرافات wire-format
Routing، model binding، filters، middleware Integration test اتّصال الإطار هو لبّ الموضوع
انتقالات حالة ViewModel أو Presenter في WPF / WinForms Unit test لا حاجة لتشغيل واجهة المستخدم
الـ binding الفعليّ، الـ dispatcher، دورة حياة الـ control، حلقة الرسائل Integration test أو UI test سلوك الإطار والـ thread هو الموضوع
مسارات الملفّات، الصلاحيّات، الأقفال، المجلّدات المشتركة، نهايات الأسطر، الترميزات Integration test يلزم سلوك OS ونظام الملفّات الحقيقيّ
تسجيل COM، 32-bit / 64-bit، STA / MTA، تحميل DLL Integration test الموضوع هو حدود البيئة والعمليّة
فحوص دخان لبدء التطبيق وحالات الاستخدام الرئيسيّة E2E / smoke test اجعل العدد قليلاً

النموذج الذهنيّ المفيد: أيّ طبقة اختبار الأقرب إلى السبب الذي سيكسر الكود في الإنتاج؟
هذا دليل أفضل من موقع الكود نفسه.

4. ما الذي يدخل في unit tests

unit tests تلائم تماماً المسؤوليّات التي يبقى لها معنى بعد إزالة العالم الخارجيّ.

أمثلة نموذجيّة:

  • قواعد الأعمال
  • منطق التفرّع
  • انتقالات الحالة
  • التحقّق من الإدخال
  • تصنيف الأخطاء
  • قرارات سياسة الـ retry
  • تغيّرات حالة ViewModel / Presenter
  • منطق التحويل نفسه

كلّما ارتفع عدد التركيبات، قَوِيَت حجّة unit tests.

مثال:

  • بكوبون / بدون كوبون
  • متوفّر / غير متوفّر
  • طلب أوّل / طلب متكرّر
  • مستخدم admin / مستخدم عاديّ
  • قيمة سليمة / قيمة حدّيّة / قيمة غير سليمة

كلّما زادت تلك التركيبات، صار تشغيلها كلّها عبر integration tests مكلفاً.
unit tests هي المكان الصحيح لتقسيم تلك الحالات تقسيماً دقيقاً.

ويهمّ كذلك أن تُبقي unit tests العواملَ الخارجيّة تحت السيطرة:

  • احقن الزمن الحاليّ
  • اجعل GUIDs أو العشوائيّة قابلةً للاستبدال
  • لا تستخدم sleep
  • لا تلمس DB حقيقيّاً ولا ملفّات حقيقيّة
  • لا تخرج إلى الشبكة الحقيقيّة

إذا التُزم بهذه القواعد، فعادةً ما تظلّ الاختبارات أكثر استقراراً بكثير.

4.1. حين يبدأ عدد الـ mock بالانفجار

إن حاولت كتابة unit test وانتهيت إلى:

  • سبعة mocks
  • كتلة setup ضخمة
  • كود arrange أطول من الـ assertion ذاته
  • لا فكرة واضحة عمّا تتحقّق منه فعلاً

فعادةً يحدث أحد أمرين:

  1. الصنف يفعل أكثر ممّا ينبغي
  2. أنت تُقحم في unit test توصيلاً يجب اختباره بوصفه integration

mocks أدوات لقطع العالم الخارجيّ. وهي ليست أدوات لإثبات صحّة الاتّصال الحقيقيّ.
إن طمست هذا الخطّ، صار من السهل جدّاً الوصول إلى “الكلّ أخضر، ومع ذلك ينكسر في الإنتاج”.

5. أربع حدود تنتمي عادةً إلى integration tests

الأماكن التي ينبغي أن ترتفع إلى integration tests تتجمّع عادةً حول التنسيق، والتوصيل، والبيئة، والزمن.

5.1. حدّ التنسيق

أعني بـ “التنسيق” هنا أموراً مثل:

  • JSON / XML / CSV
  • مخطّط DB والـ mapping
  • سلوك nullable / precision / timezone
  • تسلسل enum والتواريخ
  • الترميزات وBOM
  • نهايات الأسطر

يُشير Martin Fowler أيضاً إلى أنّ الحدود التي تتضمّن serialize / deserialize مرشَّحات قويّة لـ integration test. مقالته The Practical Test Pyramid مرجع مفيد.

من الإخفاقات النموذجيّة:

  • DTO تحوّل إلى JSON ثمّ تغيّرت أسماء الحقول
  • كَسَر اقتباس CSV أو فواصل الأسطر
  • قيم decimal تمّ تقريبها
  • DateTimeOffset تصرّف بشكل مختلف في DB
  • null والسلسلة الفارغة عُومِلتا بشكل مختلف عمّا كان متوقّعاً

هذه بالضبط الأخطاء التي تُفلت من unit tests في الغالب.

5.2. حدّ التوصيل

يغطّي حدّ التوصيل أموراً مثل:

  • تسجيل DI
  • ربط الإعدادات
  • routing
  • model binding
  • filters
  • middleware
  • بدء الـ host
  • توصيل الأحداث
  • WPF binding وتوصيل الأوامر

عند هذه الطبقة، السؤال ليس “هل دالّتي صحيحة؟”
السؤال الحقيقيّ هو “هل وُصِلت مكوّنات حقيقيّة متعدّدة وصلاً صحيحاً؟”

إرشادات ASP.NET Core ذاتها تُبقي unit tests لـ controller action مركّزة على منطق قرار الـ action، بينما يُعالَج routing وmodel binding وfilters في طبقة integration.
ينطبق المبدأ نفسه خارج تطبيقات الويب أيضاً: في تطبيقات سطح المكتب، تنتمي انتقالات حالة ViewModel إلى unit tests، بينما XAML binding الفعليّ أو سلوك Dispatcher أقرب إلى integration tests.

5.3. حدّ البيئة

هذا حدّ كبير في تطوير Windows.

  • صلاحيّات الملفّات
  • المجلّدات المشتركة
  • أقفال الملفّات
  • إعادة التسمية من الملفّات المؤقّتة
  • صلاحيّات المسؤول
  • صلاحيّات تشغيل الخدمة
  • تسجيل COM
  • سلوك 32-bit / 64-bit
  • سلوك STA / MTA
  • موضع تحميل DLL

في كلّ هذه الحالات، OS أو بيئة التشغيل ذاتها هي الموضوع الرئيسيّ.
in-memory fake يفقد كثيراً من المعنى هنا، فـ integration tests خيار أأمن.

وعلى وجه الخصوص، إن تضمّن نظامك برامج Windows قائمة أو قطعاً من COM / ActiveX، فمن الشائع جدّاً أن تنكسر الأمور أوّلاً بسبب التسجيل، أو الـ bitness، أو نموذج الـ thread، أو الصلاحيّات قبل أن تنكسر بسبب منطق الأعمال.
هذه هي الإخفاقات التي تلتقطها integration tests، ولا تستطيع unit tests التقاطها عادةً.

5.4. حدّ الزمن

من السهل أيضاً تفويت الزمن والتزامن.

  • timeout
  • cancellation
  • سلوك retry في وقت التشغيل الفعليّ
  • التنفيذ المدفوع بـ timer
  • سلوك الإيقاف للعمل الخلفيّ
  • race conditions
  • ترتيب الإيقاف

المهمّ هنا أن تفصل القرار عن السلوك الفعليّ.

مثلاً:

  • كم مرّة من retry مسموح بها
  • أيّ الاستثناءات قابل لـ retry

عادةً يمكن اختباره بـ unit test.
لكن أموراً مثل:

  • هل ينطلق timeout فعلاً
  • هل تنتشر cancellation فعلاً
  • هل يتسابق timer مع عمل async
  • هل تُغلق الـ handles أو الـ tasks بنظافة عند الإيقاف

تنتمي إلى integration tests.

6. أخطاء شائعة

6.1. الاكتفاء بـ repository مزيَّفاً

إن جعلت كلّ طبقة الـ repository mock، فأنت ما زلت لا تعلم:

  • هل SQL صحيح
  • هل تتصرّف الـ transaction تصرّفاً صحيحاً
  • هل المخطّط متطابق
  • هل ينحرف الـ mapping
  • هل تنكسر الترميزات أو الـ precision

الـ repository ليس وحدة منطق بالأكثر، بل نقطة اتّصال على حدّ.
في تلك الحالة، تستحقّ integration tests وزناً أكبر من unit tests.

6.2. محاولة اختبار الإطار من unit test لـ controller أو endpoint

في unit test لـ controller action، الأمور التي تريد عادةً التحقّق منها هي:

  • قرارات التفرّع
  • اختيار قيمة الإرجاع
  • أيّ اعتماد يُستدعى

أمّا التالية فهموم مختلفة:

  • هل يُصاب الـ route
  • هل ينجح model binding
  • هل يعمل الـ filter
  • كيف يبدو الـ pipeline بعد middleware

تلك تنتمي إلى integration tests.
إن خلطتَ الاثنين، صار من الأصعب بكثير معرفة ما الذي انكسر فعلاً.

6.3. محاولة فرض كلّ تركيبات الإدخال في integration tests

integration tests أقرب إلى العالم الحقيقيّ، فهي عادةً أبطأ.
لذلك من الأفضل التقسيم على نحو كلّ تركيبات التفرّع في unit tests، وحالات الحدود التمثيليّة في integration tests.

كذلك تقترح إرشادات Microsoft للـ integration test تضييق اختبارات DB أو نظام الملفّات إلى سيناريوهات تمثيليّة مثل read / write / update / delete، بدلاً من محاولة تشغيل كلّ نمط ممكن عبر طبقة integration.

6.4. ضرب SaaS الإنتاج أو APIs الإنتاج من CI

تلك فكرة سيّئة عادةً.

ينبغي أن تشعر integration tests بالواقعيّة، لكنّ ذلك لا يعني أنّها تحتاج إلى ضرب SaaS الإنتاج أو APIs الإنتاج في كلّ مرّة.
نصيحة Fowler هي استخدام خدمة محلّيّة، أو fake، أو instance اختبار مخصّص بدلاً من ذلك.

عمليّاً، المزيج الجيّد يكون:

  • DB محلّيّ
  • مجلّدات مؤقّتة
  • test host
  • بيئة اختبار مخصّصة
  • خدمة fake بعقد ثابت

7. هيكل عمليّ يعمل جيّداً

لا توجد نسبة صحيحة مطلقة.
ومع ذلك، فإنّ هيكلاً قابلاً لإعادة الاستخدام جدّاً هو نموذج الطبقات الثلاث:

الطبقة نوع الاختبار الرئيسيّ ما الذي يدخل هنا
طبقة الـ core unit testing مكثَّف قواعد الأعمال، انتقالات الحالة، التحقّق، تصنيف الأخطاء
طبقة الحدّ integration tests ضيّقة DB، الملفّات، HTTP، serializer، DI، الإعدادات، COM، الصلاحيّات
طبقة التطبيق ككلّ عدد صغير من smoke / E2E tests فحوص بدء التشغيل، تدفّقات المستخدم الرئيسيّة، حماية الانحدار من الإخفاقات الجادّة

القاعدة العمليّة بسيطة: unit tests تنمو بالكمّيّة، أمّا integration tests فتنمو بواقعيّة الحدود.

طريقة جيّدة للسير:

  1. عدِّد الحدود في التطبيق
  2. ادفع المنطق إلى أشكال يمكن فصلها عن العالم الخارجيّ
  3. أبقِ على الأقلّ مساراً سعيداً واحداً ومسار إخفاق تمثيليّ واحد لكلّ حدّ
  4. أبقِ مسار end-to-end صغيراً
  5. حين يظهر bug، أضف الاختبار في أرخص طبقة قادرة على إعادة إنتاج الـ bug

الخطوة الأخيرة مهمّة جدّاً.

  • إن كان الـ bug في القاعدة، أضف unit test
  • إن كان الـ bug في SQL / binding / الإعدادات / الصلاحيّات / التسجيل، أضف integration test
  • إن امتدّ الـ bug عبر بدء التشغيل أو النشر، أضف smoke أو E2E

بهذه الطريقة، تظلّ مسؤوليّة الاختبار أوضح بكثير.

8. خمسة أسئلة تطرحها حين تتردّد

حين تعلق، تساعدك هذه الأسئلة الخمسة كثيراً:

  1. إذا استبدلت الاعتماد بـ in-memory fake، هل يبقى معنى الاختبار؟
    • إن نعم، فهو يميل نحو unit test.
  2. حين يفشل هذا، هل أشكّ في المنطق أوّلاً، أم في الاتّصال / الإعدادات أوّلاً؟
    • إن كان الاتّصال أو الإعدادات هو الخطر الحقيقيّ، فهو يميل نحو integration test.
  3. هل DB / الملفّات / serializer / DI / routes / model binding / OS / الصلاحيّات / bitness / threads هي الموضوع الحقيقيّ؟
    • إن نعم، فهو يميل نحو integration test.
  4. هل أحتاج إلى تشغيل تركيبات إدخال كثيرة بسرعة؟
    • إن نعم، فهو يميل نحو unit test.
  5. حين يفشل هذا الاختبار، هل أعرف فوراً ما الذي يجب إصلاحه؟
    • إن لا، فربّما تكون طبقات الاختبار مختلطة.

هذه طريقة جيّدة لتجنّب القرار المتسرّع من نوع “يبدو واقعيّاً، إذن لا بدّ أنّه integration” / “يبدو سريعاً، إذن لا بدّ أنّه unit”.

9. خلاصة

الحدّ بين unit tests وintegration tests يُحسم على الوجه الأمثل لا بمكان وجود الكود، بل بـنوع عدم اليقين الذي تريد تقليله.

الملخّص العمليّ:

  • unit tests اختباراتُ حُكم
  • integration tests اختباراتُ اتّصال
  • تركيبات التفرّع تنتمي إلى unit tests
  • التنسيق والتوصيل والبيئة والزمن تنتمي إلى integration tests
  • التحقّق end-to-end ينبغي أن يبقى صغيراً وانتقائيّاً

أكبر الأخطاء التي يجب تجنّبها:

  • الاعتقاد بأنّ mocks تُثبت صحّة الاتّصال الحقيقيّ
  • محاولة تغطية كلّ تفرّع عبر integration tests
  • خلط مسؤوليّات unit test وintegration test

عند الشكّ، اسأل: هل الإخفاق فعلاً يخصّ الحُكم، أم يخصّ الاتّصال؟
هذا السؤال الواحد يُوضّح عدداً مفاجئاً من الحالات.

10. مقالات ذات صلة

11. مراجع

أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.

ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.

الملف الشخصي للمؤلف

صفحة الملف الشخصي لمؤلف المقالة.

غو كومورا

مؤسّس شركة كومورا سوفت ذ.م.م.

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

روابط عامة

العودة إلى المدونة