---
title: "كود صعب الاختبار"
type: "test-smell"
slug: "hard-to-test-code"
url: "http://localhost:3000/ar/test-smells/hard-to-test-code.md"
category: "روائح الاقتران"
description: "كود الإنتاج الذي يجبر تصميمه (الاقتران الوثيق، أو التبعيات المخفية، أو الحالة العامة، أو الإدخال/الإخراج غير الحتمي، أو الواجهات غير المتزامنة فقط) الاختبارات على القيام بالتواءات محرجة، أو يجعل اختبار الوحدة بشكل معزول أمراً مستحيلاً تماماً."
---
# كود صعب الاختبار

> كود الإنتاج الذي يجبر تصميمه (الاقتران الوثيق، أو التبعيات المخفية، أو الحالة العامة، أو الإدخال/الإخراج غير الحتمي، أو الواجهات غير المتزامنة فقط) الاختبارات على القيام بالتواءات محرجة، أو يجعل اختبار الوحدة بشكل معزول أمراً مستحيلاً تماماً.

## Signs and Symptoms

تعيش هذه الرائحة في كود _الإنتاج_، ولكنك تكتشفها أثناء كتابة الاختبارات. العلامة الدالة هي أنه لا يمكن كتابة اختبار وحدة بشكل نظيف — حيث يتعين على الاختبار محاربة التصميم لوضع الكود قيد الاختبار في حالة معروفة وملاحظة النتيجة.

راقب هذه الأعراض:

* **كتل ترتيب (arrange) عملاقة.** يجب عليك بناء شبكة من المتعاونين لمجرد إنشاء فئة الكود قيد الاختبار ("لا يمكنني اختبار `OrderService` دون ربط قاعدة البيانات والبوابة ومحمل الإعدادات أيضاً").
* **لا يوجد شق (seam) لحقن بديل.** يستدعي الكود `new ConcreteThing()`، أو يصل إلى كائن فردي ساكن (`Database.getInstance()`), أو يلمس الشبكة/نظام الملفات/الساعة مباشرة، لذا لا يوجد مكان لاستبدالها ببديل (stub or fake).
* **عدم الحتمية مضمن في الكود.** يعتمد المنطق على `Date.now()`، أو `Math.random()`، أو `process.env`، أو توقيت الساعة الذي لا يمكن للاختبار التحكم فيه، مما ينتج عنه نتائج متقلبة أو غير قابلة للتكرار.
* **لا توجد نقطة ملاحظة / لا توجد نقطة تحكم.** النتيجة التي تريد التحقق منها مدفونة في حالة خاصة أو تأثير جانبي، لذا تصل الاختبارات إليها عبر الانعكاس (reflection)، أو التحويل (casts)، أو وصولات مخصصة للاختبار فقط.
* **واجهة غير متزامنة فقط.** الطريقة الوحيدة لتشغيل الكود هي بدء مؤقت/خيط/طابور ثم `sleep()` أو التحقق الدوري (polling)، لأن الاكتمال لا يمكن ملاحظته مباشرة أبداً.
* **تقوم بتغيير كود الإنتاج لمجرد اختباره.** مثل تخفيف `private` إلى `public`، أو إضافة خطافات اختبار مثل `if (testMode) { ... }`، أو استخدام الفئات الفرعية فقط للوصول إلى التفاصيل الداخلية.

```js
// صعب الاختبار: تبعيات مخفية وصلبة، حالة عامة، عدم حتمية
class OrderService {
  placeOrder(cart) {
    const db = Database.getInstance();            // كائن فردي عام (لا يوجد شق)
    const gateway = new StripeGateway(API_KEY);   // تبعية ملموسة صلبة -> شبكة حقيقية
    const id = Math.random().toString(36).slice(2); // غير حتمي
    const charge = gateway.charge(cart.total);    // استدعاء HTTP حقيقي داخل الوحدة
    db.save({ id, at: Date.now(), charge });       // ساعة غير محقونة
    return id;
  }
}
// لـ "اختبار وحدة" هذا، يجب عليك الاتصال بـ Stripe، وتعديل Date/Math عالمياً،
// وفحص كائن فردي مشترك — أي إجراء اختبار غير مباشر عبر واجهات محرجة.

```

## Reasons for the Problem

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

* **كتابة الاختبارات آخراً على الكود القديم.** يسمي ميسزاروس السبب الجذري _بافتقار التصميم لقابلية الاختبار_. تنشأ قابلية الاختبار بشكل طبيعي من التطوير الموجه بالاختبار (TDD)، ولكن عندما تُكتب الاختبارات في "النهاية"، لا شيء يضغط على التصميم لإظهار نقاط التحكم والملاحظة، لذا يجب تعديلها وتجهيزها لاحقاً.
* **الاقتران الوثيق والتبعيات الصلبة.** إنشاء متعاونين ملموسين باستخدام `new` (أو سحبهم من كائنات فردية ساكنة) يعني أنه لا يمكنك استبدالهم ببدائل اختبار — فيقوم الاختبار بتشغيل الوحدة _و_كل ما تلمسه.
* **الحالة العامة/المشتركة والمدخلات المخفية.** الكائنات الفردية (singletons)، والمتغيرات الساكنة (statics)، والوقت المحيط، والعشوائية، وقراءات البيئة هي مدخلات لم يصرّح بها الاختبار أبداً ولا يمكنه تعيينها، لذا يكون السلوك ضمنياً وغير قابل للتحكم.
* **العمليات غير المتزامنة والتأثيرات الجانبية في النواة.** عندما يكون منطق العمل (business logic) متشابكاً مع الخيوط (threads)، أو الطوابير (queues)، أو الإدخال/الإخراج (IO)، أو واجهة المستخدم (UI)، فلا توجد قيمة نقية للتحقق منها.

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

* **الموثوقية.** تصبح الاختبارات المجبرة على استخدام الإدخال/الإخراج الحقيقي، أو فترات النوم (sleeps)، أو الكائنات الفردية المشتركة، أو ساعات النظام العامة بطيئة ومتقلبة ومرتبطة بالترتيب — وهو المسار الكلاسيكي للوصول إلى اختبار غير مستقر/هش.
* **سهولة الصيانة.** تهيئات التجهيز الضخمة والاختبارات التي تصل إلى التفاصيل الداخلية تقرن مجموعة الاختبارات بتفاصيل التنفيذ، لذا فإن عمليات إعادة الهيكلة غير الضارة تؤدي إلى كسر الاختبارات (الاختبار الهش / التجهيز الهش).
* **الثقة الزائفة.** يتم اختبار أصعب مسارات الكود وأهمها فقط من خلال واجهات خشنة وغير مباشرة — أو يتم تخطيها بالكامل. تبدو أرقام التغطية جيدة بينما يتم تشغيل المنطق البرمجي الأكثر خطورة بصعوبة.
* **سهولة القراءة.** الاختبار الذي تطغى عليه السقالات (scaffolding) يحجب السلوك الوحيد الذي يفترض به تحديده، وبالتالي لا يوثق شيئاً.
* في الكتالوج المفتوح لروائح الاختبار، يعتبر _الكود صعب الاختبار_ هو النظير في جانب الإنتاج لروائح الاختبار مثل _الاختبار غير المباشر (Indirect Testing)_ و _الاختبار الهش (Fragile Test)_: فالتصميم السيئ هو السبب، والاختبارات المحرجة هي العَرَض.

## Treatment

أصلح _التصميم_ وليس الاختبار. الهدف هو إعطاء كل وحدة **نقطة تحكم** (طريقة لوضعها في حالة معروفة) و **نقطة ملاحظة** (طريقة لقراءة النتيجة).

1. **أدخل شقوقاً عبر حقن التبعية (Dependency Injection).** Pass collaborators in (constructor injection) instead of constructing or looking them up. Inject ambient inputs too — clock, id/uuid generator, random source — so they become controllable parameters.
2. **اعتمد على التجريدات.** برمج بالاعتماد على واجهة (interface) واستبدلها بـ stub/fake/mock في الاختبارات. هذا يزيل مباشرة _التبعية الصلبة المترابطة_ لـ Designite ويقلل من _التبعية المفرطة_.
3. **طبق نمط الكائن المتواضع (Humble Object).** ادفع الأجزاء الصعبة الاختبار حقاً (واجهة المستخدم، غراء العمليات غير المتزامنة، الإدخال/الإخراج المجرد) إلى محول رقيق خالٍ من المنطق، وانقل منطق القرار إلى كائن بسيط متزامن يمكنك اختباره مباشرة.
4. **استخرج دوالاً نقية (pure functions).** افصل الحساب عن التأثيرات الجانبية؛ وتحقق من القيم المرجعة، وقم بإجراء عمليات الإدخال/الإخراج عند الحدود فقط.
5. **اجعل العمليات غير المتزامنة قابلة للملاحظة.** أرجع وعداً (promise) أو اعرض إشارة اكتمال، واحقن المجدول (scheduler) بحيث تستخدم الاختبارات مؤقتات وهمية بدلاً من `sleep`.
6. **بالنسبة للكود القديم الذي لا يمكنك إعادة تصميمه بعد،** استخدم _فئة فرعية مخصصة للاختبار_ أو شقاً ضيق النطاق (تقنيات فيذرز Feathers) لوضع الكود تحت الاختبار، ثم أعد الهيكلة نحو حقن التبعيات — بدلاً من ترك خطافات `if (testMode)` دائمة في كود الإنتاج.
7. **تجنب الحالة العامة/الساكنة.** استبدل الكائنات الفردية بتبعيات يتم تمريرها صراحةً حتى لا تتشارك الاختبارات الحالة المخفية أو تعيد ضبطها.

```js
// بعد: التبعيات، الساعة، والمعرف تم حقنهم -> قابل للاختبار بمعزل
class OrderService {
  constructor(db, gateway, clock = () => Date.now(), newId = uuid) {
    this.db = db; this.gateway = gateway; this.clock = clock; this.newId = newId;
  }
  placeOrder(cart) {
    const id = this.newId();
    const charge = this.gateway.charge(cart.total); // تجريد لبوابة الدفع (PaymentGateway)
    this.db.save({ id, at: this.clock(), charge });
    return id;
  }
}

// الاختبار: حتمي، لا توجد شبكة، لا يوجد تعديل عالمي، سريع.
const svc = new OrderService(fakeDb, fakeGateway, () => 1000, () => 'id-1');
expect(svc.placeOrder({ total: 50 })).toBe('id-1');
expect(fakeGateway.charge).toHaveBeenCalledWith(50);
expect(fakeDb.saved[0]).toEqual({ id: 'id-1', at: 1000, charge: fakeGateway.result });

```

## Detected by

- **designite** `التبعية الصلبة المترابطة (رائحة قابلية الاختبار)` — التبعية الصلبة المترابطة (Hard-wired Dependency) (https://www.designite-tools.com/blog/understanding-testability-test-smells)
- **designite** `التبعية المفرطة (رائحة قابلية الاختبار)` — التبعية المفرطة (https://www.designite-tools.com/blog/understanding-testability-test-smells)
- **designite** `الحالة العامة (رائحة قابلية الاختبار)` — الحالة العامة (https://www.designite-tools.com/blog/understanding-testability-test-smells)
- **designite** `انتهاك قانون ديميتر (رائحة قابلية الاختبار)` — انتهاك قانون ديميتر (https://www.designite-tools.com/blog/understanding-testability-test-smells)
