Logo

بناء البرمجيات - المعمل 3: تقنيات اختبار البرمجيات

27 دقيقة قراءة
شرائح الدرس
1 / 15

المعمل 3: تقنيات اختبار البرمجيات

التحقق (Validation)، البرمجة القائمة على الاختبار أولًا، تقنيات الصندوق الأسود/الصندوق الأبيض، والتغطية

المقرر: بناء البرمجيات
القسم: علوم الحاسب - كلية الحاسبات والمعلومات، جامعة المنصورة
الفصل الدراسي: خريف 2025
الأسبوع: 3


📋 جدول المحتويات

  1. التحقق (Validation)
  2. التحقق مقابل التوثّق (Validation vs Verification)
  3. الجودة المثالية في البرمجيات
  4. لماذا يُعدّ اختبار البرمجيات صعبًا
  5. البرمجة القائمة على الاختبار أولاً
  6. تقنيات اختبار البرمجيات
  7. اختبار الصندوق الأسود مقابل الصندوق الأبيض
  8. التقنيات القائمة على المواصفات
  9. التقنيات القائمة على الخبرة
  10. اختيار حالات الاختبار عبر التقسيم
  11. التغطية (Coverage)
  12. اختبار الوحدة واختبار التكامل
  13. تمارين المعمل

1. التحقق (Validation)

ما هو التحقق (Validation)؟

الاختبار مثال على عملية أعمّ تُسمّى التحقق (Validation). الغرض من التحقق هو اكتشاف المشكلات في البرنامج، وبالتالي زيادة ثقتك في صحة البرنامج.

ثلاثة أنواع من التحقق

graph TD
    A[Validation] --> B[Verification]
    A --> C[Code Review]
    A --> D[Testing]
    
    B --> B1[Constructs formal proof<br/>that program is correct]
    B --> B2[Tedious to do by hand]
    B --> B3[Automated tool support<br/>still in research]
    
    C --> C1[Have somebody else<br/>carefully read your code]
    C --> C2[Reason informally<br/>about correctness]
    C --> C3[Good way to uncover bugs]
    
    D --> D1[Run program on<br/>carefully selected inputs]
    D --> D2[Check the results]
    
    style A fill:#9b59b6,stroke:#333,stroke-width:3px,color:#fff

1. التوثّق الشكلي (Verification)

  • يبني برهانًا رسميًا (Formal proof) على أن البرنامج صحيح
  • مُرهِق عند القيام به يدويًا
  • دعم الأدوات الآلية للتوثّق الشكلي لا يزال مجال بحث نشِط

2. مراجعة الكود (Code Review)

  • أن يقرأ شخص آخر الكود الخاص بك بعناية
  • ويستدلّ بشكل غير رسمي حول صحته
  • يمكن أن تكون طريقة جيدة لاكتشاف الأخطاء

3. الاختبار (Testing)

  • تشغيل البرنامج على مدخلات مختارة بعناية
  • التحقق من النتائج

2. التحقق مقابل التوثّق (Validation vs Verification)

graph LR
    A[Verification] -->|Question| B["Are we building<br/>the product RIGHT?"]
    C[Validation] -->|Question| D["Are we building<br/>the RIGHT product?"]
    
    style A fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
    style C fill:#3498db,stroke:#333,stroke-width:2px,color:#fff

التوثّق الشكلي (Verification): هل نبني المنتج بالطريقة الصحيحة؟

التحقق (Validation): هل نبني المنتج الصحيح؟


3. الجودة المثالية في البرمجيات

حتى مع أفضل عمليات التحقق، من الصعب جدًا تحقيق جودة مثالية في البرمجيات.

معدّلات العيوب المتبقية النموذجية

فيما يلي بعض معدّلات العيوب المتبقية النموذجية (الأخطاء المتبقية بعد إطلاق البرنامج) لكل kloc (ألف سطر من الكود المصدري):

مستوى الجودةعدد العيوب لكل KLOCأمثلة
1 - 10 عيوب/klocبرمجيات صناعية نموذجيةمعظم البرمجيات التجارية
0.1 - 1 عيب/klocتحقق عالي الجودةقد تحقق مكتبات جافا هذا المستوى
0.01 - 0.1 عيب/klocأفضل تحقق ممكن، حرِج للسلامةناسا وشركات مثل Praxis
graph TD
    A[Software Quality Levels] --> B[Typical Industry<br/>1-10 defects/kloc]
    A --> C[High Quality<br/>0.1-1 defects/kloc]
    A --> D[Safety Critical<br/>0.01-0.1 defects/kloc]
    
    B --> B1[Most commercial software]
    C --> C1[Java libraries]
    D --> D1[NASA, Praxis]
    
    style B fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
    style C fill:#f39c12,stroke:#333,stroke-width:2px
    style D fill:#2ecc71,stroke:#333,stroke-width:2px

الواقع

قد يكون هذا مثبِّطًا بالنسبة للأنظمة الكبيرة. على سبيل المثال:

  • إذا أطلقت مليون سطر من الكود المصدري الصناعي النموذجي (عيب واحد لكل kloc)
  • فهذا يعني أنك فاتك 1000 خطأ!

4. لماذا يُعدّ اختبار البرمجيات صعبًا

ثلاثة تحديات رئيسية

mindmap
  root((Why Testing<br/>is Hard))
    Exhaustive Testing
      Infeasible
      Too many test cases
      2^64 for 32-bit multiply
    Haphazard Testing
      Less likely to find bugs
      No confidence increase
      Just try and see
    Random Testing
      Doesn't work for software
      Physical tricks don't apply
      No uniformity assumption

1. الاختبار الشامل (Exhaustive Testing) غير قابل للتطبيق

عادةً ما تكون مساحة حالات الاختبار الممكنة كبيرة جدًا بحيث يتعذّر تغطيتها بشكل شامل.

مثال: الاختبار الشامل لعملية ضرب أعداد عائمة (Floating-point) بحجم 32 بت a*b

  • توجد 2^64 حالة اختبار!
  • هذا غير قابل للتنفيذ حسابيًا

2. الاختبار العشوائي غير المنظّم (Haphazard Testing) غير فعّال

الاختبار العشوائي غير المنظّم ("جرّبه فقط وانظر هل يعمل") أقلّ احتمالاً لاكتشاف الأخطاء.

  • كما أنه لا يزيد من ثقتنا في صحة البرنامج

3. الاختبار العشوائي أو الإحصائي لا يعمل جيدًا مع البرمجيات

تستطيع تخصصات هندسية أخرى:

  • اختبار عيّنات عشوائية صغيرة (مثلاً 1% من الأقراص الصلبة المصنَّعة)
  • استنتاج معدّل العيوب لكامل دفعة الإنتاج
  • استخدام حِيَل عديدة لتسريع الوقت
    • مثال: فتح ثلاجة 1000 مرة خلال 24 ساعة بدلاً من 10 سنوات

لماذا لا تنجح هذه الحِيَل مع البرمجيات:

  • تعطي هذه الحِيَل معدّلات فشل معروفة (مثل متوسط عمر القرص الصلب)
  • تفترض استمرارية أو تجانسًا (Uniformity) عبر مساحة العيوب
  • هذا صحيح بالنسبة للمصنوعات المادية (Physical artifacts)
  • لكنه غير صحيح بالنسبة للبرمجيات - فالأخطاء منفصلة (Discrete)، لا مستمرّة

5. البرمجة القائمة على الاختبار أولاً (Test-First Programming)

عقلية الاختبار

graph LR
    A[When Coding] --> B[Goal: Make it WORK]
    C[When Testing] --> D[Goal: Make it FAIL]
    
    B --> E[Build the program]
    D --> F[Break the program]
    
    style A fill:#2ecc71,stroke:#333,stroke-width:2px
    style C fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff

يتطلّب الاختبار امتلاك الموقف الصحيح:

  • عندما تبرمج، هدفك أن تجعل البرنامج يعمل
  • لكن بصفتك مختبِرًا، تريد أن يفشل

عليك أن تكون قاسيًا:

  • المختبِر الجيد يحمل مطرقة ثقيلة
  • ويضرب البرنامج في كل مكان قد يكون فيه ضعيفًا
  • حتى يمكن التخلّص من تلك الثغرات

اختبر مبكرًا وباستمرار

لا تؤجّل الاختبار إلى النهاية!

من الأمتع بكثير أن تختبر الكود الخاص بك أثناء تطويره.

عملية البرمجة القائمة على الاختبار أولاً

graph TD
    A[1. Write Specification] --> B[2. Write Tests]
    B --> C[3. Write Code]
    C --> D{Tests Pass?}
    D -->|No| C
    D -->|Yes| E[Done!]
    
    style A fill:#3498db,stroke:#333,stroke-width:2px,color:#fff
    style B fill:#f39c12,stroke:#333,stroke-width:2px
    style C fill:#9b59b6,stroke:#333,stroke-width:2px,color:#fff
    style E fill:#2ecc71,stroke:#333,stroke-width:2px

في البرمجة القائمة على الاختبار أولاً، تكتب الاختبارات قبل أن تكتب أي كود على الإطلاق.

يسير تطوير دالة واحدة بهذا الترتيب:

  1. اكتب مواصفة (Specification) للدالة
  2. اكتب اختبارات تُمارِس المواصفة
  3. اكتب الكود الفعلي
    • بمجرد أن يجتاز الكود الخاص بك الاختبارات التي كتبتها، تكون قد انتهيت!

لماذا الاختبار أولاً؟

المواصفة (Specification) تصف سلوك الإدخال والإخراج للدالة:

  • تحدّد أنواع المعاملات (Parameters) وأي قيود إضافية عليها
    • مثال: يجب أن يكون معامل sqrt غير سالب
  • تحدّد نوع القيمة المُعادة (Return value)
  • تصف كيف ترتبط القيمة المُعادة بالمدخلات

كتابة الاختبارات أولاً طريقة جيدة لفهم المواصفة:

  • يمكن أن تحتوي المواصفة نفسها على أخطاء
    • غير صحيحة، أو غير مكتملة، أو غامضة، أو تفتقد حالات حدّية (Corner cases)
  • محاولة كتابة الاختبارات يمكن أن تكشف هذه المشكلات مبكرًا
  • قبل أن تُضيّع وقتك في كتابة تنفيذ لمواصفة معيبة

6. تقنيات اختبار البرمجيات

ما هي تقنية اختبار البرمجيات؟

تساعدك تقنيات اختبار البرمجيات على تصميم حالات اختبار أفضل.

بما أن الاختبار الشامل غير ممكن، تساعد هذه التقنيات على:

  • تقليل عدد حالات الاختبار الواجب تنفيذها
  • مع زيادة تغطية الاختبار
  • تحديد شروط اختبار يصعب التعرّف عليها بطرق أخرى

ثلاثة أنواع من تقنيات الاختبار

graph TD
    A[Testing Techniques] --> B[Specification-Based<br/>Black-Box]
    A --> C[Structure-Based<br/>White-Box]
    A --> D[Experience-Based]
    
    B --> B1[Boundary Value Analysis]
    B --> B2[Equivalence Partitioning]
    B --> B3[Decision Table Testing]
    B --> B4[State Transition Diagrams]
    
    C --> C1[Based on code structure]
    C --> C2[Uses knowledge of<br/>implementation]
    
    D --> D1[Error Guessing]
    D --> D2[Exploratory Testing]
    
    style A fill:#9b59b6,stroke:#333,stroke-width:3px,color:#fff
    style B fill:#3498db,stroke:#333,stroke-width:2px,color:#fff
    style C fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
    style D fill:#f39c12,stroke:#333,stroke-width:2px

7. اختبار الصندوق الأسود مقابل الصندوق الأبيض

اختبار الصندوق الأسود (Black-box Testing)

graph LR
    A[Test Cases] --> B[BLACK BOX<br/>???]
    B --> C[Results]
    D[Specification<br/>Only] -.-> A
    
    style B fill:#2c3e50,stroke:#333,stroke-width:2px,color:#fff

اختبار الصندوق الأسود (Blackbox testing) يعني اختيار حالات الاختبار من المواصفة فقط، وليس من تنفيذ الدالة.

  • تختبر بناءً على المتطلبات والمواصفات
  • لا تنظر إلى الكود
  • لا تعرف كيف يعمل داخليًا

اختبار الصندوق الأبيض (White-box Testing / Glass Box Testing)

graph LR
    A[Test Cases] --> B[WHITE BOX<br/>See Inside]
    B --> C[Results]
    D[Code Structure<br/>Implementation] -.-> A
    
    style B fill:#ecf0f1,stroke:#333,stroke-width:2px

اختبار الصندوق الأبيض (Whitebox testing) (يُسمّى أيضًا اختبار الصندوق الزجاجي - Glass box testing) يعني اختيار حالات الاختبار مع معرفة كيفية تنفيذ الدالة فعليًا.

أمثلة:

  • إذا كان التنفيذ يختار خوارزميات مختلفة حسب المدخل
    • فينبغي عليك التقسيم وفقًا لتلك النطاقات
  • إذا كان التنفيذ يحتفظ بـذاكرة تخزين مؤقت داخلية (Cache) تتذكّر إجابات المدخلات السابقة
    • فينبغي عليك اختبار المدخلات المتكررة

ملاحظة مهمة لاختبار الصندوق الأبيض

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

مثال:

  • إذا كانت المواصفة تقول "يرمي استثناءً (Exception) إذا كان المدخل بتنسيق سيئ"
  • يجب ألا يتحقق اختبارك تحديدًا من NullPointerException لمجرد أن هذا ما يفعله التنفيذ الحالي
  • تسمح المواصفة برمي أي استثناء
  • يجب أن تكون حالة الاختبار الخاصة بك عامّة بالمثل للحفاظ على حرية المُنفِّذ

8. التقنيات القائمة على المواصفات

1. تحليل القيم الحدّية (Boundary Value Analysis - BVA)

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

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

graph LR
    A[Valid Range] --> B[MIN]
    B --> C[Valid Values]
    C --> D[MAX]
    
    E[Below MIN] -.->|Test| B
    F[Above MAX] -.->|Test| D
    
    style E fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
    style F fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff
    style B fill:#2ecc71,stroke:#333,stroke-width:2px
    style D fill:#2ecc71,stroke:#333,stroke-width:2px

الفكرة الأساسية: غالبًا ما تحدث الأخطاء عند حدود نطاقات الإدخال.

اختبر عند:

  • القيمة الدنيا (Minimum)
  • أعلى القيمة الدنيا مباشرة
  • القيمة العظمى (Maximum)
  • أسفل القيمة العظمى مباشرة
  • القيم غير الصالحة (أقل من الحد الأدنى، أعلى من الحد الأقصى)

2. تقسيم التكافؤ (Equivalence Partitioning - EP)

التعريف: تُقسَّم بيانات إدخال الاختبار إلى عدد من الفئات (Classes) ذات بيانات متكافئة. ثم تُصمَّم حالات الاختبار لكل فئة أو قسم.

الغرض: يساعد هذا على تقليل عدد حالات الاختبار مع الحفاظ على تغطية جيدة.

graph TD
    A[Input Domain] --> B[Partition 1]
    A --> C[Partition 2]
    A --> D[Partition 3]
    
    B --> B1[Pick ONE<br/>representative test]
    C --> C1[Pick ONE<br/>representative test]
    D --> D1[Pick ONE<br/>representative test]
    
    style A fill:#9b59b6,stroke:#333,stroke-width:2px,color:#fff

الفكرة الأساسية: يجب أن تتصرّف جميع القيم في قسم واحد بالطريقة نفسها.

  • اختبر قيمة واحدة من كل قسم
  • إذا فشل اختبار واحد في قسم، فمن المرجّح أن تفشل بقية القيم في القسم نفسه أيضًا

3. اختبار جدول القرار (Decision Table Testing)

التعريف: تُصمَّم حالات الاختبار بناءً على جداول القرار (Decision tables) التي تُصاغ باستخدام توليفات مختلفة من المدخلات ومخرجاتها المقابلة، بناءً على شروط وسيناريوهات متنوعة تلتزم بقواعد عمل مختلفة.

الفكرة الأساسية: اختبر بشكل منهجي جميع توليفات الشروط.

مثال على البنية:

الشرط 1الشرط 2الشرط 3النتيجة المتوقعة
صحيحصحيحصحيحالإجراء أ
صحيحصحيحخطأالإجراء ب
صحيحخطأصحيحالإجراء ج
............

4. مخططات انتقال الحالة (State Transition Diagrams)

التعريف: يُنظر إلى البرنامج قيد الاختبار كنظام له عدد محدود من الحالات (States) من أنواع مختلفة. يُوجَّه الانتقال من حالة إلى أخرى بمجموعة من القواعد.

متى تُستخدم: يمكن تطبيق هذه التقنية على الأنظمة التي تحتوي على مسارات عمل (Workflows) معيّنة داخلها.

stateDiagram-v2
    [*] --> StateA
    StateA --> StateB: event1
    StateB --> StateC: event2
    StateC --> StateA: event3
    StateC --> [*]

الفكرة الأساسية: اختبر الانتقالات بين الحالات، مع التأكد من:

  • عمل الانتقالات الصالحة بشكل صحيح
  • رفض الانتقالات غير الصالحة

9. التقنيات القائمة على الخبرة

تعتمد هذه التقنيات بشكل كبير على خبرة المختبِر لفهم أهم المناطق في البرنامج.

تعتمد النتائج على مهارات ومعرفة وخبرة الأشخاص المعنيين.

graph TD
    A[Experience-Based<br/>Techniques] --> B[Error Guessing]
    A --> C[Exploratory Testing]
    
    B --> B1[Anticipate errors based<br/>on experience]
    B --> B2[Knowledge of product failures]
    B --> B3[Dependent on skills<br/>and intuition]
    
    C --> C1[Test without formal<br/>documentation]
    C --> C2[Minimum time for planning<br/>Maximum for execution]
    C --> C3[Test design and execution<br/>performed concurrently]
    
    style A fill:#f39c12,stroke:#333,stroke-width:3px

1. تخمين الأخطاء (Error Guessing)

يتوقّع المختبِرون الأخطاء بناءً على:

  • خبرتهم
  • توفّر البيانات
  • معرفتهم بحالات فشل المنتج

يعتمد تخمين الأخطاء على:

  • مهارات المختبِرين
  • حدسهم
  • خبرتهم

2. الاختبار الاستكشافي (Exploratory Testing)

تُستخدم هذه التقنية لاختبار التطبيق دون أي توثيق رسمي.

الخصائص:

  • وقت أدنى متاح للتخطيط
  • وقت أقصى لتنفيذ الاختبار
  • يتم تصميم الاختبار وتنفيذه بشكل متزامن

10. اختيار حالات الاختبار عبر التقسيم (Partitioning)

التحدي

لا يمكننا اختبار كل شيء بشكل شامل، لذا نحتاج إلى اختيار حالات الاختبار بذكاء.

مثال: الدالة BigInteger.multiply()

لننظر إلى مثال. BigInteger صنف مدمج في مكتبة جافا يمكنه تمثيل أعداد صحيحة من أي حجم (بخلاف الأنواع البدائية int وlong التي لها نطاقات محدودة فقط).

/**
 * @param val another BigInteger
 * @return a BigInteger whose value is (this * val)
 */
public BigInteger multiply(BigInteger val)

مثال على الاستخدام:

BigInteger a = ...;
BigInteger b = ...;
BigInteger ab = a.multiply(b);

تقسيم مساحة الإدخال

graph TD
    A[multiply function] --> B[Input Space:<br/>BigInteger × BigInteger]
    B --> C[Two-dimensional input space<br/>pairs of integers a, b]

توقيع الدالة (Function signature): multiply: BigInteger × BigInteger → BigInteger

لدينا مساحة إدخال ثنائية الأبعاد، تتكوّن من جميع أزواج الأعداد الصحيحة (a, b).

استراتيجية التقسيم

بالتفكير في كيفية عمل عملية الضرب، قد نبدأ بهذه الأقسام:

1. توليفات الإشارة (Sign Combinations)

graph TD
    A[Sign Partitions] --> B[a and b both positive]
    A --> C[a and b both negative]
    A --> D[a is positive, b is negative]
    A --> E[a is negative, b is positive]
    
    style A fill:#3498db,stroke:#333,stroke-width:2px,color:#fff
  • a وb كلاهما موجب
  • a وb كلاهما سالب
  • a موجب، وb سالب
  • a سالب، وb موجب

2. الحالات الخاصة

توجد أيضًا بعض الحالات الخاصة لعملية الضرب يجب التحقق منها:

قيم خاصة: 0، 1، و-1

  • a أو b يساوي 0
  • a أو b يساوي 1
  • a أو b يساوي -1

3. الأقسام المبنية على الحجم

بصفتنا مختبِرين حذِرين نحاول إيجاد أخطاء، قد نشكّ في أن مُنفِّذ BigInteger ربما:

  • حاول جعله أسرع باستخدام int أو long داخليًا عند الإمكان
  • ولم يلجأ إلى تمثيل عام مكلِف (مثل قائمة من الأرقام) إلا عندما تكون القيمة كبيرة جدًا

لذا ينبغي أن نختبر:

  • a أو b صغير (يتّسع ضمن أنواع الأعداد الصحيحة القياسية)
  • القيمة المطلقة لـa أو b أكبر من Long.MAX_VALUE
    • أكبر عدد صحيح بدائي ممكن في جافا، أي حوالي 2^63

تقسيم كامل

mindmap
  root((BigInteger multiply<br/>Test Partitions))
    Sign
      Both positive
      Both negative
      Mixed signs
    Special Values
      Zero
      One
      Negative one
    Size
      Small numbers
      Large numbers > Long.MAX_VALUE

ملخّص التقسيم

بتقسيم مساحة الإدخال بذكاء:

  • نحدّد فئات مختلفة من المدخلات
  • نختار حالات اختبار تمثيلية من كل فئة
  • نحقّق تغطية جيدة دون الحاجة لاختبار شامل

11. التغطية (Coverage)

ما هي التغطية؟

التغطية (Coverage) مقياس يحدّد مقدار الكود الذي تُمارِسه مجموعة اختباراتك.

تغطية الجمل البرمجية (Statement Coverage)

graph TD
    A[Code Statements] --> B[Statement 1]
    A --> C[Statement 2]
    A --> D[Statement 3]
    
    B --> E{Executed?}
    C --> F{Executed?}
    D --> G{Executed?}
    
    E -->|Yes| H[Covered]
    E -->|No| I[Not Covered]
    
    style H fill:#2ecc71,stroke:#333,stroke-width:2px
    style I fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff

تغطية الجمل البرمجية (Statement coverage) تقيس نسبة الجمل البرمجية التي تم تنفيذها.

مثال:

def absolute_value(x):
    if x < 0:           # Line 1
        x = -x          # Line 2
    return x            # Line 3

اختبار بـ x = 5:

  • ينفّذ الأسطر: 1، 3
  • لا ينفّذ: السطر 2
  • التغطية: 66% (سطران من أصل 3)

اختبار بـ x = -5:

  • ينفّذ الأسطر: 1، 2، 3
  • التغطية: 100%

تغطية الفروع (Branch Coverage)

تغطية الفروع أقوى من تغطية الجمل البرمجية.

تغطية الفروع (Branch coverage) تقيس ما إذا تم اختبار كل نقطة قرار (if/else) في كلا الاتجاهين (صحيح وخطأ).

graph TD
    A{Decision} -->|True| B[Branch 1]
    A -->|False| C[Branch 2]
    
    B --> D{Both branches<br/>tested?}
    C --> D
    
    D -->|Yes| E[100% Branch Coverage]
    D -->|No| F[Incomplete Coverage]
    
    style E fill:#2ecc71,stroke:#333,stroke-width:2px
    style F fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff

مثال:

def classify(x):
    if x > 0:                    # Branch A
        if x % 2 == 0:           # Branch B
            return "positive even"
        else:
            return "positive odd"
    elif x < 0:                  # Branch C
        return "negative"
    else:
        return "zero"

للحصول على تغطية فروع 100%، تحتاج إلى اختبارات تغطي:

  • الفرع A: صحيح وخطأ
  • الفرع B: صحيح وخطأ (عندما يكون A صحيحًا)
  • الفرع C: صحيح وخطأ (عندما يكون A خطأً)

تغطية المسارات (Path Coverage)

تغطية المسارات (Path coverage) تقيس جميع المسارات الممكنة عبر الكود.

المشكلة: غالبًا ما تكون تغطية المسارات غير عملية لأن عدد المسارات ينمو أُسّيًا مع عدد القرارات.

أهداف التغطية

graph LR
    A[Coverage Target] --> B[Minimum: 70-80%<br/>Statement]
    A --> C[Good: 85-90%<br/>Branch]
    A --> D[Path coverage<br/>Often impractical]
    
    style B fill:#f39c12,stroke:#333,stroke-width:2px
    style C fill:#2ecc71,stroke:#333,stroke-width:2px
    style D fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff

مهم: تغطية 100% لا تعني خلوّ الكود من الأخطاء!

  • التغطية تقيس فقط ما تم تنفيذه
  • لا تضمن أن الكود صحيح

12. اختبار الوحدة واختبار التكامل

اختبار الوحدة (Unit Testing)

graph TD
    A[System] --> B[Module A]
    A --> C[Module B]
    A --> D[Module C]
    
    B --> B1[Unit Test A]
    C --> C1[Unit Test B]
    D --> D1[Unit Test C]
    
    style B1 fill:#2ecc71,stroke:#333,stroke-width:2px
    style C1 fill:#2ecc71,stroke:#333,stroke-width:2px
    style D1 fill:#2ecc71,stroke:#333,stroke-width:2px

التعريف: اختبار الوحدة (Unit test) هو اختبار يختبر وحدة (Module) منفردة (دالة أو صنف) في عزلة (Isolation) إن أمكن.

لماذا الاختبار في عزلة؟

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

استخدام البدائل الوهمية (Stubs)

سيحتوي البرنامج المختبَر جيدًا على اختبارات لكل وحدة منفردة.

لاختبار الوحدات في عزلة، قد تحتاج إلى استخدام بدائل وهمية (Stubs):

  • البديل الوهمي (Stub) هو بديل مبسّط لتبعية (Dependency) ما
  • يسمح لك باختبار وحدة دون الحاجة إلى التبعية الحقيقية

اختبار التكامل (Integration Testing)

graph TD
    A[Module A] --> C[Integration Point]
    B[Module B] --> C
    C --> D[Module C]
    
    E[Integration Test] -.-> C
    
    style E fill:#e74c3c,stroke:#333,stroke-width:2px,color:#fff

التعريف: اختبار التكامل (Integration test) يختبر مجموعة من الوحدات، أو حتى البرنامج بأكمله.

الغرض:

  • اختبار الوحدات في عزلة ليس كافيًا
  • يمكن أن يفشل البرنامج عند نقاط الاتصال بين الوحدات
  • مثال: قد تتوقّع وحدة ما مدخلات مختلفة عمّا تتلقّاه فعليًا من وحدة أخرى

اختبارات الوحدة مقابل اختبارات التكامل

graph TD
    A[Testing Strategy] --> B[Many Unit Tests]
    A --> C[Fewer Integration Tests]
    
    B --> B1[Fast execution]
    B --> B2[Easy debugging]
    B --> B3[Test individual logic]
    
    C --> C1[Slower execution]
    C --> C2[Harder debugging]
    C --> C3[Test interactions]
    
    style B fill:#2ecc71,stroke:#333,stroke-width:2px
    style C fill:#f39c12,stroke:#333,stroke-width:2px

أفضل الممارسات:

  • امتلك مجموعة شاملة من اختبارات الوحدة تمنحك ثقة في الوحدات المنفردة
  • بعدها سيكون لديك بحث أقلّ بكثير للعثور على الخطأ عندما يفشل اختبار تكامل

مقارنة:

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

13. تمارين المعمل

التمرين 1: تطبيق البرمجة القائمة على الاختبار أولاً

المسألة: أنشئ دالة is_valid_password(password) تتحقق من صحة كلمات المرور.

المواصفة:

def is_valid_password(password):
    """
    Validates a password based on the following rules:
    - Minimum 8 characters
    - Must contain at least one uppercase letter
    - Must contain at least one lowercase letter
    - Must contain at least one digit
    
    Returns: (is_valid: bool, message: str)
    """

مهمتك:

  1. اكتب حالات الاختبار أولاً (قبل التنفيذ)
  2. ثم نفّذ الدالة
  3. شغّل الاختبارات وتأكد من نجاحها

التمرين 2: تطبيق تحليل القيم الحدّية

المسألة: اختبر دالة calculate_discount(age) التي تُعيد نسبة الخصم:

  • العمر 0-17: خصم 20%
  • العمر 18-64: خصم 0%
  • العمر 65+: خصم 15%

مهمتك:

  1. حدّد الحدود (Boundaries)
  2. صمّم حالات اختبار باستخدام تحليل القيم الحدّية (BVA)
  3. اختبر: أسفل الحد الأدنى، عند الحد الأدنى، أعلى الحد الأدنى مباشرة، وهكذا

التمرين 3: استخدام تقسيم التكافؤ

المسألة: اختبر دالة classify_grade(score):

  • 90-100: 'A'
  • 80-89: 'B'
  • 70-79: 'C'
  • 60-69: 'D'
  • أقل من 60: 'F'

مهمتك:

  1. حدّد أقسام التكافؤ (Equivalence partitions)
  2. اختر حالة اختبار تمثيلية واحدة من كل قسم
  3. نفّذ واختبر

التمرين 4: تحقيق تغطية الكود

المسألة: بالنظر إلى هذه الدالة، اكتب اختبارات لتحقيق تغطية فروع 100%:

def process_order(amount, is_member, has_coupon):
    discount = 0
    
    if is_member:
        discount = 10
    
    if has_coupon:
        discount += 5
    
    if amount > 100:
        discount += 5
    
    final_price = amount * (1 - discount/100)
    return final_price

مهمتك:

  1. حدّد جميع الفروع
  2. صمّم حالات اختبار لتغطية جميع الفروع
  3. احسب نسبة التغطية
  4. تحقّق من تحقيق تغطية فروع 100%

التمرين 5: اختبار الوحدة مقابل اختبار التكامل

المسألة: لديك ثلاث وحدات:

  • validate_input(data) - يتحقق من صحة مدخل المستخدم
  • process_data(data) - يعالج البيانات التي تم التحقق منها
  • save_to_database(result) - يحفظ النتيجة

مهمتك:

  1. اكتب اختبارات وحدة لكل وحدة (اختبر في عزلة باستخدام البدائل الوهمية Stubs)
  2. اكتب اختبارات تكامل لمسار العمل الكامل
  3. اشرح الفرق فيما يتحقق منه كل نوع من الاختبارات

الملخّص

أهم النقاط

mindmap
  root((Software Testing))
    Validation
      Verification
      Code Review
      Testing
    Test-First
      Write tests before code
      Increases confidence
      Finds spec bugs early
    Techniques
      Black-box: BVA, EP
      White-box: Coverage
      Experience-based
    Coverage
      Statement coverage
      Branch coverage
      Not a guarantee
    Testing Levels
      Unit tests: isolation
      Integration: connections
      Both needed
  1. يشمل التحقق (Validation) التوثّق الشكلي ومراجعة الكود والاختبار
  2. البرمجة القائمة على الاختبار أولاً تساعد على اكتشاف الأخطاء مبكرًا وتحسين التصميم
  3. اختبار الصندوق الأسود يستخدم المواصفة فقط، بينما الصندوق الأبيض يستخدم معرفة التنفيذ
  4. استراتيجيات التقسيم تساعد على اختيار حالات اختبار جيدة بكفاءة
  5. مقاييس التغطية توضّح مقدار الكود الذي جرى اختباره، لكنها لا تضمن الصحة
  6. اختبارات الوحدة سريعة ومركّزة، بينما اختبارات التكامل تتحقق من الاتصالات

تذكّر

  • الاختبار الشامل مستحيل - استخدم استراتيجيات ذكية
  • اختبر مبكرًا وباستمرار - لا تنتظر حتى النهاية
  • كن قاسيًا عند الاختبار - حاول كسر الكود الخاص بك
  • استخدم تقنيات متعددة - لا توجد تقنية واحدة تكتشف كل الأخطاء
  • اهدف إلى تغطية عالية - لكن افهم حدودها
  • وازن بين اختبارات الوحدة والتكامل - كلاهما مهم

نهاية المعمل 3 - تقنيات اختبار البرمجيات

كلية الحاسبات والمعلومات، جامعة المنصورة - خريف 2025

جامعة المنصورة

  • الفصل الدراسي الأول - 2020-2021
  • قسم علوم الحاسب
  • كلية الحاسبات والمعلومات
  • [CS ---] بناء البرمجيات
  • الفرقة: 4
  • المعمل: 3
  • د. عمر الزكي
  • عبد الرحمن جمال

جدول المحاضرة

  • التحقق (Validation)
  • البرمجة القائمة على الاختبار أولاً
  • ما هي تقنية اختبار البرمجيات؟
  • أنواع تقنيات اختبار البرمجيات؟
  • اختبار الصندوق الأسود والصندوق الأبيض
  • التغطية (Coverage)
  • اختبار الوحدة
  • 2

التحقق (Validation)

  • الاختبار مثال على عملية أعمّ تُسمّى التحقق. الغرض من التحقق هو اكتشاف المشكلات في البرنامج، وبالتالي زيادة ثقتك في صحة البرنامج. يشمل التحقق:
  • الاستدلال الشكلي حول البرنامج، ويُسمّى عادةً التوثّق الشكلي (Verification). يبني التوثّق الشكلي برهانًا رسميًا على أن البرنامج صحيح. التوثّق الشكلي مُرهِق عند القيام به يدويًا، ولا يزال دعم الأدوات الآلية له مجال بحث نشِط
  • مراجعة الكود. أن يقرأ شخص آخر الكود الخاص بك بعناية، ويستدلّ بشكل غير رسمي حوله، يمكن أن تكون طريقة جيدة لاكتشاف الأخطاء
  • الاختبار. تشغيل البرنامج على مدخلات مختارة بعناية والتحقق من النتائج.
  • 3

التحقق مقابل التوثّق الشكلي (Validation vs Verification)

  • تشمل عملية التوثّق الشكلي التحقق من الوثائق والتصميم والكود والبرنامج | آلية ديناميكية لاختبار المنتج الفعلي والتحقق منه
  • لا تتضمن تنفيذ الكود | تتضمن دائمًا تنفيذ الكود
  • يُتحقق فيها مما إذا كان البرنامج مطابقًا للمواصفة | تتحقق مما إذا كان البرنامج يلبّي متطلبات وتوقعات العميل
  • تكتشف الأخطاء مبكرًا في دورة التطوير | يمكنها اكتشاف أخطاء لا تستطيع عملية التوثّق الشكلي اكتشافها
  • الهدف هو التطبيق والبنية المعمارية للبرنامج، والمواصفة، والتصميم الكامل، والمستوى العالي، وتصميم قاعدة البيانات وغيرها | الهدف هو المنتج الفعلي
  • تأتي قبل التحقق | تأتي بعد التوثّق الشكلي
  • 4

الجودة المثالية في البرمجيات

  • حتى مع أفضل عمليات التحقق، من الصعب جدًا تحقيق جودة مثالية في البرمجيات. فيما يلي بعض معدّلات العيوب المتبقية النموذجية (الأخطاء المتبقية بعد إطلاق البرنامج) لكل kloc (ألف سطر من الكود المصدري):
  • 1 - 10 عيوب/kloc: برمجيات صناعية نموذجية.
  • 0.1 - 1 عيب/kloc: تحقق عالي الجودة. قد تحقق مكتبات جافا هذا المستوى من الصحة.
  • 0.01 - 0.1 عيب/kloc: أفضل تحقق ممكن، حرِج للسلامة. يمكن لناسا وشركات مثل Praxis تحقيق هذا المستوى.
  • قد يكون هذا مثبِّطًا بالنسبة للأنظمة الكبيرة. على سبيل المثال، إذا أطلقت مليون سطر من الكود المصدري الصناعي النموذجي (عيب واحد لكل kloc)، فهذا يعني أنك فاتك 1000 خطأ!
  • 5

لماذا يُعدّ اختبار البرمجيات صعبًا

  • 6
  • فيما يلي بعض الأساليب التي، للأسف، لا تعمل بشكل جيد في عالم البرمجيات.
  • الاختبار الشامل غير قابل للتطبيق. عادةً ما تكون مساحة حالات الاختبار الممكنة كبيرة جدًا بحيث يتعذّر تغطيتها بشكل شامل. تخيّل الاختبار الشامل لعملية ضرب أعداد عائمة بحجم 32 بت، a*b. توجد 2^64 حالة اختبار!
  • الاختبار العشوائي غير المنظّم ("جرّبه فقط وانظر هل يعمل") أقلّ احتمالاً لاكتشاف الأخطاء، كما أنه لا يزيد من ثقتنا في صحة البرنامج.
  • الاختبار العشوائي أو الإحصائي لا يعمل جيدًا مع البرمجيات. تستطيع تخصصات هندسية أخرى اختبار عيّنات عشوائية صغيرة (مثلاً 1% من الأقراص الصلبة المصنَّعة) واستنتاج معدّل العيوب لكامل دفعة الإنتاج. يمكن للأنظمة المادية استخدام حِيَل عديدة لتسريع الوقت، مثل فتح ثلاجة 1000 مرة خلال 24 ساعة بدلاً من 10 سنوات. تعطي هذه الحِيَل معدّلات فشل معروفة (مثل متوسط عمر القرص الصلب)، لكنها تفترض استمرارية أو تجانسًا عبر مساحة العيوب. هذا صحيح بالنسبة للمصنوعات المادية.

يتطلّب الاختبار امتلاك الموقف الصحيح. عندما تبرمج، هدفك أن تجعل البرنامج يعمل، لكن بصفتك مختبِرًا، تريد أن يفشل.

  • بدلاً من ذلك، عليك أن تكون قاسيًا. المختبِر الجيد يحمل مطرقة ثقيلة ويضرب البرنامج في كل مكان قد يكون فيه ضعيفًا، حتى يمكن التخلّص من تلك الثغرات.
  • 7
  • البرمجة القائمة على الاختبار أولاً

البرمجة القائمة على الاختبار أولاً

  • اختبر مبكرًا وباستمرار.
  • لا تؤجّل الاختبار إلى النهاية، فمن الأمتع بكثير أن تختبر الكود الخاص بك أثناء تطويره.
  • في البرمجة القائمة على الاختبار أولاً، تكتب الاختبارات قبل أن تكتب أي كود على الإطلاق. يسير تطوير دالة واحدة بهذا الترتيب:
  • اكتب مواصفة للدالة.
  • اكتب اختبارات تُمارِس المواصفة.
  • اكتب الكود الفعلي. بمجرد أن يجتاز الكود الخاص بك الاختبارات التي كتبتها، تكون قد انتهيت.
  • 8

البرمجة القائمة على الاختبار أولاً

  • تصف المواصفة سلوك الإدخال والإخراج للدالة.
  • تحدّد أنواع المعاملات وأي قيود إضافية عليها (مثلاً يجب أن يكون معامل sqrt غير سالب).
  • كما تحدّد نوع القيمة المُعادة وتصف كيف ترتبط القيمة المُعادة بالمدخلات.
  • كتابة الاختبارات أولاً طريقة جيدة لفهم المواصفة. يمكن أن تحتوي المواصفة نفسها على أخطاء أيضًا - غير صحيحة، أو غير مكتملة، أو غامضة، أو تفتقد حالات حدّية. محاولة كتابة الاختبارات يمكن أن تكشف هذه المشكلات مبكرًا، قبل أن تُضيّع وقتك في كتابة تنفيذ لمواصفة معيبة.
  • 9

ما هي تقنية اختبار البرمجيات؟

  • تساعدك تقنيات اختبار البرمجيات على تصميم حالات اختبار أفضل. بما أن الاختبار الشامل غير ممكن؛ تساعد تقنيات الاختبار اليدوي على تقليل عدد حالات الاختبار الواجب تنفيذها مع زيادة تغطية الاختبار. كما تساعد على تحديد شروط اختبار يصعب التعرّف عليها بطرق أخرى.
  • أنواع تقنيات اختبار البرمجيات:-
  • تقنيات قائمة على المواصفات (تقنيات الصندوق الأسود)
  • تقنيات قائمة على البنية (تقنيات الصندوق الأبيض)
  • تقنيات قائمة على الخبرة
  • 10

اختبار الصندوق الأسود مقابل الصندوق الأبيض

  • 11

اختبار الصندوق الأسود مقابل الصندوق الأبيض

  • 12

اختبار الصندوق الأسود مقابل الصندوق الأبيض

  • 13
  • اختبار الصندوق الأسود يعني اختيار حالات الاختبار من المواصفة فقط، وليس من تنفيذ الدالة
  • اختبار الصندوق الأبيض (يُسمّى أيضًا اختبار الصندوق الزجاجي) يعني اختيار حالات الاختبار مع معرفة كيفية تنفيذ الدالة فعليًا. فمثلاً، إذا كان التنفيذ يختار خوارزميات مختلفة حسب المدخل، فينبغي عليك التقسيم وفقًا لتلك النطاقات. إذا كان التنفيذ يحتفظ بذاكرة تخزين مؤقت داخلية تتذكّر إجابات المدخلات السابقة، فينبغي عليك اختبار المدخلات المتكررة.
  • اختبار الصندوق الأبيض
  • عند إجراء اختبار الصندوق الأبيض، يجب أن تحرص على ألا تتطلّب حالات الاختبار الخاصة بك سلوك تنفيذ محدَّد لم تنصّ عليه المواصفة تحديدًا. فمثلاً، إذا كانت المواصفة تقول "يرمي استثناءً إذا كان المدخل بتنسيق سيئ"، فيجب ألا يتحقق اختبارك تحديدًا من NullPointerException لمجرد أن هذا ما يفعله التنفيذ الحالي. تسمح المواصفة في هذه الحالة برمي أي استثناء، لذا يجب أن تكون حالة الاختبار الخاصة بك عامّة بالمثل للحفاظ على حرية المُنفِّذ. سنتحدث أكثر عن هذا في محاضرة المواصفات.

1. التقنيات القائمة على المواصفات أو تقنيات الصندوق الأسود

  • تحليل القيم الحدّية (BVA)
  • تُطبَّق هذه التقنية لاستكشاف الأخطاء عند حدود نطاق الإدخال. يكتشف تحليل القيم الحدّية أي أخطاء إدخال قد تتعارض مع عمل البرنامج بشكل صحيح.
  • تقسيم التكافؤ (EP)
  • في تقسيم التكافؤ، تُقسَّم بيانات إدخال الاختبار إلى عدد من الفئات ذات بيانات متكافئة. ثم تُصمَّم حالات الاختبار لكل فئة أو قسم. يساعد هذا على تقليل عدد حالات الاختبار.
  • اختبار جدول القرار
  • في هذه التقنية، تُصمَّم حالات الاختبار بناءً على جداول القرار التي تُصاغ باستخدام توليفات مختلفة من المدخلات ومخرجاتها المقابلة، بناءً على شروط وسيناريوهات متنوعة تلتزم بقواعد عمل مختلفة.
  • مخططات انتقال الحالة
  • في هذه التقنية، يُنظر إلى البرنامج قيد الاختبار كنظام له عدد محدود من الحالات من أنواع مختلفة. يُوجَّه الانتقال من حالة إلى أخرى بمجموعة من القواعد. تحدّد القواعد الاستجابة للمدخلات المختلفة. يمكن تطبيق هذه التقنية على الأنظمة التي تحتوي على مسارات عمل معيّنة داخلها.
  • 14

3. التقنيات القائمة على الخبرة

  • تعتمد هذه التقنيات بشكل كبير على خبرة المختبِر لفهم أهم المناطق في البرنامج. تعتمد نتائج هذه التقنيات على مهارات ومعرفة وخبرة الأشخاص المعنيين. أنواع التقنيات القائمة على الخبرة هي كالتالي:
  • تخمين الأخطاء
  • في هذه التقنية، يتوقّع المختبِرون الأخطاء بناءً على خبرتهم وتوفّر البيانات ومعرفتهم بحالات فشل المنتج. يعتمد تخمين الأخطاء على مهارات المختبِرين وحدسهم وخبرتهم.
  • الاختبار الاستكشافي
  • تُستخدم هذه التقنية لاختبار التطبيق دون أي توثيق رسمي. يوجد وقت أدنى متاح للتخطيط ووقت أقصى لتنفيذ الاختبار. في الاختبار الاستكشافي، يتم تصميم الاختبار وتنفيذه بشكل متزامن.
  • 15

اختيار حالات الاختبار عبر التقسيم

  • 16

تابع..

  • 17
  • مثال: الدالة BigInteger.multiply()
  • لننظر إلى مثال. BigInteger صنف مدمج في مكتبة جافا يمكنه تمثيل أعداد صحيحة من أي حجم،
  • بخلاف الأنواع البدائية int وlong التي لها نطاقات محدودة فقط.
  • يمتلك BigInteger دالة multiply تضرب قيمتين من نوع BigInteger معًا:
  • /**
  • @param val another BigIntger
  • @return a BigInteger whose value is (this * val).
  • public BigInteger multiply(BigInteger val) على سبيل المثال، إليك كيف يمكن استخدامها: BigInteger a = ...;
  • BigInteger b = ...;
  • BigInteger ab = a.multiply(b);

تابع..

  • 18
  • multiply : BigInteger × BigInteger → BigInteger
  • إذن لدينا مساحة إدخال ثنائية الأبعاد، تتكوّن من جميع أزواج الأعداد الصحيحة (a,b). الآن
  • لنقسّمها. بالتفكير في كيفية عمل عملية الضرب، قد نبدأ بهذه الأقسام:
  • a وb كلاهما موجب
  • a وb كلاهما سالب
  • a موجب، وb سالب
  • a سالب، وb موجب
  • توجد أيضًا بعض الحالات الخاصة لعملية الضرب يجب التحقق منها: 0، 1، و-1.
  • a أو b يساوي 0، أو 1، أو -1

تابع..

  • أخيرًا، بصفتنا مختبِرين حذِرين نحاول إيجاد أخطاء
  • قد نشكّ في أن مُنفِّذ BigInteger ربما حاول جعله أسرع باستخدام int أو long داخليًا عند الإمكان، ولم يلجأ إلى تمثيل عام مكلِف (مثل قائمة من الأرقام) إلا عندما تكون القيمة كبيرة جدًا. لذا ينبغي بالتأكيد أن نجرّب أيضًا أعدادًا صحيحة كبيرة جدًا، أكبر من أكبر قيمة long.
  • a أو b صغير
  • القيمة المطلقة لـa أو b أكبر من Long.MAX_VALUE، أكبر عدد صحيح بدائي ممكن في جافا، وهو حوالي 2^63.
  • 19

تابع..

  • 20

التغطية (Coverage)

  • 21

تغطية الجمل البرمجية

  • 22

تغطية الجمل البرمجية

  • 23

التغطية (Coverage)

  • 24

اختبار الوحدة - اختبار التكامل

  • 25
  • اختبار الوحدة والبدائل الوهمية (Stubs)
  • سيحتوي البرنامج المختبَر جيدًا على اختبارات لكل وحدة منفردة (حيث الوحدة هي دالة أو صنف) يحتويها. يُسمّى الاختبار الذي يختبر وحدة منفردة، في عزلة إن أمكن، اختبار وحدة (Unit test). اختبار الوحدات في عزلة يؤدي إلى تصحيح أخطاء أسهل بكثير. عندما يفشل اختبار وحدة لوحدة ما، يمكنك أن تكون أكثر ثقة بأن الخطأ موجود في تلك الوحدة، بدلاً من أن يكون في أي مكان في البرنامج.
  • عكس اختبار الوحدة هو اختبار التكامل (Integration test)، الذي يختبر مجموعة من الوحدات، أو حتى البرنامج بأكمله. إذا كان كل ما لديك هو اختبارات تكامل، فعندما يفشل اختبار، عليك البحث عن الخطأ. قد يكون في أي مكان في البرنامج. لا تزال اختبارات التكامل مهمة، لأن البرنامج يمكن أن يفشل عند نقاط الاتصال بين الوحدات. مثلاً، قد تتوقّع وحدة ما مدخلات مختلفة عمّا تتلقّاه فعليًا من وحدة أخرى. لكن إذا كانت لديك مجموعة شاملة من اختبارات الوحدة تمنحك ثقة في صحة الوحدات المنفردة، فسيكون لديك بحث أقلّ بكثير للعثور على الخطأ.