---
title: "منطق الاختبار في كود الإنتاج"
type: "test-smell"
slug: "test-logic-in-production"
url: "http://localhost:3000/ar/test-smells/test-logic-in-production.md"
category: "روائح الاقتران"
description: "يحتوي كود الإنتاج على منطق أو فروع أو أعضاء موجودة فقط لدعم عملية الاختبار، مما يؤدي إلى عدم وضوح الحدود بين ما يتم تسليمه فعلياً للعملاء وما يتم اختباره فقط."
---
# منطق الاختبار في كود الإنتاج

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

## Signs and Symptoms

تجد كوداً في _النظام الخاضع للاختبار_ (SUT) يهم فقط عندما يتم تشغيل الاختبار. العلامات الدالة:

* **خطافات الاختبار / علامات الوضع (Test hooks / mode flags)** — فروع تعتمد على علامات مثل `testing`، أو `isTest`، أو `NODE_ENV === 'test'`، أو `mock` والتي تتجاوز السلوك الحقيقي.
* **أعضاء "للاختبار فقط"** — دوال setter أو getter أو `reset()` عامة أو دوال بناء مضافة فقط لتمكين الاختبار من الوصول إلى الحالة الداخلية، وغالباً ما يتم وسمها بـ `@VisibleForTesting` / `@TestOnly` وتستدعى بالفعل في كود الإنتاج.
* **تلوث المساواة (Equality pollution)** — إضافة منطق `equals()` أو منطق المقارنة إلى فئة إنتاجية لمجرد تمكين التحقق من مقارنة كائنين.
* **اعتماد الإنتاج على الاختبار** — قيام وحدات الإنتاج باستيراد إطار عمل اختبار أو هيكل اختبار أو مصنع كائنات محاكاة (mock factory).

```ts
// مشكلة (SMELL): يتصرف الكود المشحون بشكل مختلف عند تفعيل وضع "الاختبار"
class PaymentService {
  charge(order: Order) {
    if (process.env.NODE_ENV === 'test' || this.isTesting) {
      return { status: 'ok', id: 'FAKE-TEST-ID' }; // لا يتم أبداً تشغيل بوابة الدفع الحقيقية
    }
    return this.gateway.charge(order); // <-- المسار الذي يتم شحنه وتشغيله فعلياً
  }
}

```

الفرع "الحقيقي" هو الفرع الذي يصل إليه عملاؤك، وهو بالضبط الفرع الذي تتجاهله اختباراتك.

## Reasons for the Problem

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

* يكون النظام الخاضع للاختبار (SUT) صعب الاختبار (يتواصل مع شبكة، أو ساعة النظام، أو بوابة دفع، أو نظام ملفات) وتكون إضافة اختصار `if (testing)` أسرع من تقديم واجهة فصل مناسبة (proper seam).
* يحتاج الاختبار إلى مراقبة أو ضبط الحالة الداخلية، لذا يقوم المطور بإتاحتها "من أجل الاختبار فقط".
* تكون محاكاة الكائنات (mocking/stubbing) صعبة أو غير مريحة، لذا يتم ترميز بيانات جاهزة مسبقاً (canned data) خلف علامة (flag).

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

* **الثقة الزائفة.** تقوم الاختبارات بتشغيل فرع الاختبار فقط، وبالتالي يتم شحن مسار الإنتاج _بدون اختبار_. نجاح الاختبار يثبت أن الكود المزيف يعمل، وليس الكود الفعلي.
* **الموقوفية / الأمان.** قد يتم تشغيل الكود المخصص للاختبار فقط في بيئة الإنتاج الفعلية بالخطأ. القصة الكلاسيكية التحذيرية التي يذكرها "ميسزاروس" هي صاروخ Ariane 5: حيث تُرك كود مخصص للعمل الأرضي فقط نشطاً أثناء الرحلة مما تسبب في الفشل. إن ترك شرط `if (isTesting)` مفعلاً في كود الإنتاج هو المكافئ البرمجي لذلك الخطأ.
* **الأمن البرمجي.** تجاوزات الاختبار هي بمثابة أبواب خلفية (backdoors) — فعلامة تتخطى المصادقة أو الدفع أو التحقق لا يفصلها عن الاستغلال الأمني سوى خطأ واحد في التهيئة والإعداد.
* **المقروئية وتضخم واجهة برمجة التطبيقات (API).** تزيد دوال set/get أو `reset()` المخصصة للاختبار فقط من مساحة السطح العام للفئة وتضلل المستخدمين الفعليين حول الغرض الأساسي من الفئة.
* **قابلية الصيانة.** يعيش سلوكان مختلفان في فئة واحدة؛ ومع كل تغيير يجب التفكير في كل من مسار الإنتاج ومسار الاختبار، ويتعفن هذا الاختلاف بصمت مع مرور الوقت.

## Treatment

أخرج منطق الاختبار من كود الإنتاج من خلال تقديم _واجهة فصل مناسبة (seam)_ بدلاً من استخدام العلامات (flags).

1. **احقن التغيير (حقن التبعية + بديل الاختبار).** استبدل الفرع المشفر بزميل (collaborator) يستبدله الاختبار. يقوم كود الإنتاج بتوصيل التطبيق الحقيقي؛ بينما يقوم الاختبار بتوصيل كائن مزيف/بديل/محاكى.
2. **استخدم فئة فرعية مخصصة للاختبار (Test-Specific Subclass)** عندما تحتاج فقط إلى تجاوز دالة واحدة — وتجاوزها في فئة فرعية تعيش في كود الاختبار، وليس عبر شرط `if` في الفئة الأساسية.
3. **طبق نمط الكائن المتواضع (Humble Object pattern)** لسحب المنطق الذي يصعب اختباره (مثل الساعة، أو عمليات الإدخال/الإخراج) خلف محول رفيع (thin adapter)، بحيث يصبح المنطق الأساسي قابلاً للاختبار مباشرة دون الحاجة لخطافات.
4. **انقل منطق المقارنة إلى جانب الاختبار.** بدلاً من تلويث كود الإنتاج بـ `equals()` من أجل التحققات، استخدم أداة مطابقة/مقارنة مخصصة أو تحقق من الحقول التي تهمك فقط.
5. **ابقِ كود الاختبار خارج عملية البناء (build).** استخدم الفصل بين مجموعات المصادر/تكوين البناء بحيث لا يمكن تجميع المساعدين في الملف المشحون النهائي؛ وميز الأعضاء المرئية للاختبار بـ `@VisibleForTesting`/`@TestOnly` ودع أداة التحليل الإملائي (linter) تفرض عدم استدعاء كود الإنتاج لها أبداً.

```ts
// قبل: خطاف اختبار داخل كود الإنتاج
class PaymentService {
  charge(order: Order) {
    if (this.isTesting) return { status: 'ok', id: 'FAKE-TEST-ID' };
    return this.gateway.charge(order);
  }
}

// بعد: مسار كود واحد؛ يتم حقن بوابة الدفع ومحاكاتها في الاختبار
class PaymentService {
  constructor(private gateway: PaymentGateway) {}
  charge(order: Order) {
    return this.gateway.charge(order); // نفس مسار الكود في الإنتاج والاختبار
  }
}

// اختبار
const fakeGateway = { charge: () => ({ status: 'ok', id: 'FAKE-TEST-ID' }) };
const service = new PaymentService(fakeGateway);

```

الآن مسار الإنتاج هو المسار _الوحيد_، ويتحكم الاختبار في السلوك من الخارج.

## Detected by

- **codeql** `java/visible-for-testing-abuse` — استخدام VisibleForTesting في كود الإنتاج (https://codeql.github.com/codeql-query-help/java/java-visible-for-testing-abuse/)
- **deepsource** `JAVA-A1067` — لا ينبغي استخدام دوال @VisibleForTesting/@TestOnly في كود غير مخصص للاختبار (https://deepsource.com/directory/java/issues/JAVA-A1067)
- **android-lint** `VisibleForTests` — مرئي للاختبارات فقط (Visible Only For Tests) (https://googlesamples.github.io/android-custom-lint-rules/checks/VisibleForTests.md.html)
