---
title: "التهيئة في الباني"
type: "test-smell"
slug: "constructor-initialization"
url: "http://localhost:3000/ar/test-smells/constructor-initialization.md"
category: "روائح التجهيزات"
description: "يقوم فئة الاختبار بتهيئة حقول التجهيز (fixture fields) الخاصة به في الباني (constructor) بدلاً من خطاف التهيئة المخصص لإطار العمل (setUp / @BeforeEach / TestInitialize)، مما يؤدي إلى تجاوز دورة حياة الاختبار."
---
# التهيئة في الباني

> يقوم فئة الاختبار بتهيئة حقول التجهيز (fixture fields) الخاصة به في الباني (constructor) بدلاً من خطاف التهيئة المخصص لإطار العمل (setUp / @BeforeEach / TestInitialize)، مما يؤدي إلى تجاوز دورة حياة الاختبار.

## Signs and Symptoms

تحتوي فئة الاختبار على باني صريح يبني النظام تحت الاختبار، أو الكائنات الوهمية (mocks)، أو الحقول المشتركة — وهو العمل الذي ينتمي إلى خطاف التهيئة الخاص بإطار العمل (`setUp()` / `@Before` / `@BeforeEach` في JUnit، أو `[TestInitialize]` في MSTest، أو `beforeEach` في Jest/Vitest).

العلامات الدالة:

* تعلن فئة الاختبار عن باني يسند القيم لحقول ثابتة (`final`) أو حقول مثيل (instance fields).
* تعيش تهيئة التجهيز خارج دورة الحياة التي يديرها إطار العمل بالفعل — لذلك يتم تجاوز `@BeforeEach`/`@AfterEach`، وتهيئة الفئة الأساسية، وإعادة التهيئة لكل اختبار بصمت، أو تشغيلها بترتيب محير مقارنة بالباني.
* التهيئة غير المتزامنة مستحيلة: لا يمكنك استخدام `await` داخل الباني، مما يجبر عمل التجهيز غير المتزامن على التواجد في المكان الخاطئ أو تكراره.

النموذج النموذجي في JUnit (من كتالوج روائح الاختبار):

```java
public class TagEncodingTest extends BrambleTestCase {
    private final CryptoComponent crypto;
    private final SecretKey tagKey;

    public TagEncodingTest() {                 // رائحة: التهيئة في الباني
        crypto = new CryptoComponentImpl(new TestSecureRandomProvider());
        tagKey = TestUtils.getSecretKey();
    }
}

```

نظير JS/TS — بناء التجهيز مرة واحدة على مستوى نطاق `describe` أو نطاق الوحدة (مكافئ الباني) بدلاً من بنائه لكل اختبار:

```ts
describe('Cart', () => {
  const cart = new Cart();          // يُبنى مرة واحدة، ويسرب الحالة عبر الاختبارات
  it('adds an item', () => { cart.add(apple); expect(cart.size).toBe(1); });
  it('starts empty', () => { expect(cart.size).toBe(0); }); // يفشل: يرى التفاحة (apple)
});

```

## Reasons for the Problem

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

* يلجأ المطورون غير المعتادين على الغرض من خطاف التهيئة (setup hook) إلى ميزة اللغة التي يعرفونها بالفعل — الباني (constructor) — لتهيئة الحقول.
* يبدو الأمر أكثر نظافة: الحقول الثابتة (`final`) بالإضافة إلى الباني تعطي شعوراً بالتوافق مع اللغة، وقد يقوم محرّر الأكواد (IDE) بإنشاء الباني نيابةً عنك.
* التقليد الأعمى (Cargo-culting) من فئات الإنتاج، حيث تكون التهيئة في الباني _هي_ النمط الصحيح.

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

* **يتجاوز دورة حياة الاختبار.** تضمن أطر العمل حالة جديدة لكل اختبار عبر خطاف التهيئة وزوج `@BeforeEach`/`@AfterEach` (أو `beforeEach`/`afterEach`). المنطق المخفي في الباني يقع خارج هذا العقد، لذا قد لا تنطبق عمليات التنظيف، وتهيئة الفئات الأساسية، وضمانات الترتيب كما هو متوقع.
* **تسرب الحالة وعدم الاستقرار (Flakiness).** في أطر العمل التي تعيد استخدام نفس المثيل (أو عند تخزين التجهيز في متغير مشترك أو على مستوى نطاق `describe`)، تتسرب التعديلات من اختبار إلى آخر. تنجح الاختبارات أو تفشل اعتماداً على ترتيب التنفيذ — مما يؤدي إلى ثقة زائفة وفشل متقطع.
* **لا توجد تهيئة غير متزامنة.** لا يمكن للباني استخدام `await`. أي تهيئة تحتاج إلى إدخال/إخراج (I/O)، أو اتصال بقاعدة البيانات، أو خادم بدأ تشغيله لا يمكن أن توجد هناك بشكل نظيف؛ ويتحايل الناس على ذلك باستخدام استدعاءات حاجزة (blocking calls) أو كود مكرر لكل اختبار.
* **سهولة القراءة / الاتساق.** يتوقع المراجعون والأدوات وجود التجهيزات في الخطاف التقليدي. يؤدي تقسيم التهيئة بين الباني وطريقة التهيئة إلى جعل التجهيز الحقيقي صعب العثور عليه وسهل الوقوع في الخطأ.

**تنبيه — يعتمد هذا على إطار العمل.** هذه ليست قاعدة عامة. في **xUnit.net**، يعتبر الباني _هو_ التهيئة المعتادة لكل اختبار (مقروناً بـ `IDisposable` للتفكيك)، كما أن MSTest توفر قاعدة اختيارية معاكسة (`MSTEST0020`) تفضل البواني على `[TestInitialize]`. تعامل مع "التهيئة في الباني" كرائحة تحديداً عندما يوفر إطار عملك خطاف تهيئة مخصصاً (JUnit، أو MSTest الكلاسيكي، أو NUnit) وتقوم أنت بتجاوزه.

## Treatment

انقل تهيئة الحقول خارج الباني وإلى خطاف التهيئة الخاص بإطار العمل، ويفضل استخدام حالة جديدة لكل اختبار بدلاً من المثيلات المشتركة.

1. احذف باني فئة الاختبار؛ صرّح بالحقول وأسندها في خطاف التهيئة (`@BeforeEach`/`setUp()` لـ JUnit، أو `[TestInitialize]` لـ MSTest، أو `beforeEach` لـ Jest/Vitest).
2. إذا كانت التهيئة غير متزامنة، فهذا إلزامي — حيث يمكن للخطاف استخدام `await`؛ بينما لا يمكن للباني ذلك.
3. في JS/TS، ابنِ التجهيز _داخل_ `beforeEach` (إعادة تعيين متغير `let`) بدلاً من بنائه مرة واحدة في نطاق `describe` أو نطاق الوحدة، حتى يحصل كل اختبار على كائن نظيف.
4. حافظ على تماثل التفكيك: اربط `@BeforeEach` مع `@AfterEach` (أو، في نمط الباني الخاص بـ xUnit.net، قم بتنفيذ واجهة `IDisposable`).

قبل ← بعد (JUnit):

```java
// قبل
public class TagEncodingTest {
    private final CryptoComponent crypto = new CryptoComponentImpl(...);
}

// بعد
public class TagEncodingTest {
    private CryptoComponent crypto;

    @BeforeEach
    void setUp() {
        crypto = new CryptoComponentImpl(...);
    }
}

// بعد

```

قبل ← بعد (Jest/Vitest):

```ts
describe('Cart', () => {
  let cart: Cart;
  beforeEach(() => { cart = new Cart(); }); // fresh per test
  it('adds an item', () => { cart.add(apple); expect(cart.size).toBe(1); });
  it('starts empty', () => { expect(cart.size).toBe(0); });
});

```

إذا كان التجهيز غير قابل للتغيير (immutable) ومكلفاً بالفعل (وإطار العمل يدعم ذلك)، فإن استخدام خطاف لمرة واحدة مثل `@BeforeAll`/`beforeAll` مقبول — ولكن فقط للحالة المشتركة للقراءة فقط، وليس للكائنات التي تعدلها الاختبارات.

## Detected by

- **tsDetect (كاشف روائح الاختبار)** `التهيئة في الباني` (https://testsmells.org/pages/testsmells.html)
- **محللات MSTest Roslyn** `MSTEST0019` (https://learn.microsoft.com/en-us/dotnet/core/testing/mstest-analyzers/mstest0019)
