---
title: "منطق الاختبار الشرطي"
type: "test-smell"
slug: "conditional-test-logic"
url: "http://localhost:3000/ar/test-smells/conditional-test-logic.md"
category: "الروائح الغامضة"
description: "اختبار يستخدم `if`/`switch`/ternaries أو الحلقات أو `try`/`catch` لتحديد ما سيتم تشغيله أو التحقق منه، بحيث يعتمد سلوكه — وما إذا كان يتحقق من أي شيء على الإطلاق — على الفرع الذي يتم تنفيذه في وقت التشغيل."
---
# منطق الاختبار الشرطي

> اختبار يستخدم `if`/`switch`/ternaries أو الحلقات أو `try`/`catch` لتحديد ما سيتم تشغيله أو التحقق منه، بحيث يعتمد سلوكه — وما إذا كان يتحقق من أي شيء على الإطلاق — على الفرع الذي يتم تنفيذه في وقت التشغيل.

## Signs and Symptoms

يبدو الاختبار وكأنه برنامج صغير بدلاً من أن يكون سيناريو خطياً يعتمد على "التجهيز → التنفيذ → التحقق" (arrange → act → assert). ابحث عن تدفق التحكم داخل جسم الاختبار:

* استخدام `if`/`else`، أو `switch`، أو التعبيرات الشرطية الثلاثية، أو الاختصارات باستخدام `&&`/`||` والتي تحدد أي من التحققات سيتم تشغيله.
* حلقات `for`/`while`/`forEach` التي تبني المدخلات أو تكرر التحققات.
* استخدام `try`/`catch` لـ "اختبار" مسار الخطأ، مع إخفاء استدعاءات `expect` داخل `catch`.
* إعادة استخدام اختبار واحد لعدة حالات عن طريق التفرع بناءً على علامة أو حالة البيئة.
* حساب القيم المتوقعة داخل الاختبار (غالباً باستخدام حلقة أو معادلة) بدلاً من كتابتها بشكل ثابت.

```js
// رائحة: توجد التحققات داخل كود شرطي/متفرع
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) {
    expect(price(user)).toBe(80);   // قد لا يتم تشغيله أبداً
  } else {
    expect(price(user)).toBe(100);  // قد لا يتم تشغيله أبداً
  }
});

test('throws on bad input', () => {
  try {
    parse('!!!');
    // إذا لم تقم parse() بإلقاء خطأ، فسننتقل للسطر التالي دون التحقق من شيء ← ينجح الاختبار
  } catch (err) {
    expect(err.message).toMatch(/invalid/);
  }
});

```

وضع الفشل الدال: يظل الاختبار باللون الأخضر (ناجحاً) حتى عندما يكون الكود معطلاً، لأن الفرع الذي يحتوي على التحقق لم يتم الدخول إليه مطلقاً.

## Reasons for the Problem

**لماذا يحدث ذلك**

* **المبالغة في تطبيق مبدأ DRY (عدم التكرار).** يحاول المطورون تغطية عدة سيناريوهات باختبار واحد "مرن"، متفرعين بناءً على المدخلات أو علامة (flag) بدلاً من كتابة اختبار واحد لكل حالة (ميسزاروس: _الاختبار المرن_).
* **الاقتران بالبيئة.** لم يتم فصل النظام تحت الاختبار (SUT) عن تبعياته، لذا يتكيف الاختبار مع أي حالة يجدها في وقت التشغيل.
* **التفكيك الدفاعي.** يتسلل استخدام `if (resource) resource.close()` لتجنب تفكيك التجهيزات التي قد لا تكون موجودة (_التفكيك المعقد_).
* **التوقعات المحسوبة.** يتم اشتقاق النتيجة المتوقعة بنفس خوارزمية كود الإنتاج، مما يسحب هذا المنطق — بالحلقات وكل شيء — إلى الاختبار (_منطق الإنتاج في الاختبار_).
* **اختبار مسار الخطأ يدوياً.** يتم استخدام `try`/`catch` للتحقق من خطأ تم إلقاؤه بدلاً من استخدام مطابق مدمج.

**لماذا يضر ذلك**

* **الثقة الزائفة (الجزء الأسوأ).** معظم بيئات التشغيل تفشل الاختبار فقط عندما يلقي التحقق خطأً. إذا تم تخطي فرع التحقق — أو لم يلق الكود قيد الاختبار خطأً داخل `try` — ينجح الاختبار دون التحقق من _أي شيء_.
* **كود اختبار غير مختبر.** الفروع والحلقات في الاختبار هي منطق ليس له اختبارات خاصة به؛ وبالتالي فإن أي خلل في تدفق التحكم الخاص بالاختبار يمر دون أن يلاحظه أحد.
* **ضعف التشخيص.** عندما يفشل اختبار متفرع، يجب عليك أولاً تحديد _أي_ مسار قد تم تشغيله قبل أن تتمكن من تفسير الفشل.
* **صعوبة القراءة والصيانة.** يوثق الاختبار الخطي سلوكاً واحداً بنتيجة متوقعة واحدة؛ أما الاختبار المتفرع فيجبر القارئ على محاكاة التنفيذ لمعرفة ما هو مضمون فعلياً.
* **الهشاشة.** الاختبارات التي تعتمد على حالة وقت التشغيل أو البيئة تنجح أو تفشل بشكل غير حتمي.

## Treatment

اجعل كل اختبار مساراً خطياً واحداً وغير مشروط. بشكل ملموس:

1. **سيناريو واحد لكل اختبار.** قسّم الاختبار المتفرع إلى اختبارات منفصلة، أو استخدم واجهة برمجة التطبيقات المعتمدة على البيانات في إطار العمل (مثل `test.each`، أو `it.each`، أو الاختبارات البارامترية) بحيث تكون كل حالة بمثابة تشغيل مستقل مسمى بوضوح ويتم الإبلاغ عنه بشكل مستقل.
2. **ارفع التفرع خارج الاختبار.** إذا كانت الحالة تنطبق فقط تحت شرط معين، فحدد ذلك في وقت _التعريف_ (على سبيل المثال: اختيار `describe`/`it` بناءً على الإعدادات)، وليس داخل جسم الاختبار — وبذلك تظل التحققات نفسها غير مشروطة.
3. **اكتب القيم المتوقعة بشكل ثابت (Hard-code).** استبدل التوقعات المحسوبة بنتائج متوقعة صريحة (أو كائن متوقع _Expected Object_ / مطابِق مخصص). لا تعِد كتابة منطق الإنتاج داخل الاختبار.
4. **اختبر مسارات الأخطاء باستخدام المطابقات،** وليس `try`/`catch`: مثل `expect(fn).toThrow(...)`، أو `await expect(p).rejects.toThrow(...)`. فهذه تفشل بوضوح إذا لم يتم إلقاء أي خطأ.
5. **إذا كان التحقق الشرطي لا مفر منه حقاً،** فثبت العدد باستخدام `expect.assertions(n)` / `expect.hasAssertions()` بحيث يفشل الفرع الذي تم تخطيه بدلاً من النجاح الصامت.
6. **استبدل التفكيك الشرطي** بخطافات دورة حياة إطار العمل (`afterEach`) والتنظيف التلقائي/المتطابق (idempotent)، بحيث لا تكون هناك حاجة لاستخدام `if` لحماية عملية التفكيك.

```js
// قبل — اختبار متفرع، قد يتم تخطي التحققات
test('user discount', () => {
  const user = getUser();
  if (user.isPremium) expect(price(user)).toBe(80);
  else                expect(price(user)).toBe(100);
});

// بعد — حالة صريحة واحدة لكل صف، كل تحقق يتم تشغيله دائماً
test.each([
  ['premium', { isPremium: true },  80],
  ['regular', { isPremium: false }, 100],
])('price for %s user', (_label, user, expected) => {
  expect(price(user)).toBe(expected);
});

// قبل — try/catch ينجح عندما لا يتم إلقاء أي شيء
try { parse('!!!'); } catch (e) { expect(e.message).toMatch(/invalid/); }

// بعد — يفشل إذا لم تقم parse() بإلقاء خطأ
expect(() => parse('!!!')).toThrow(/invalid/);

```

## Detected by

- **eslint-jest** `jest/no-conditional-expect` — no-conditional-expect (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-conditional-expect.md)
- **eslint-jest** `jest/no-conditional-in-test` — no-conditional-in-test (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/no-conditional-in-test.md)
- **eslint-vitest** `vitest/no-conditional-expect` — no-conditional-expect (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-expect.md)
- **eslint-vitest** `vitest/no-conditional-in-test` — no-conditional-in-test (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-in-test.md)
- **eslint-vitest** `vitest/no-conditional-tests` — no-conditional-tests (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/no-conditional-tests.md)
