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

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

## Signs and Symptoms

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

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

* **ينجح الاختبار في المجموعة الكاملة ولكنه يفشل عند تشغيله بمفرده** (عبر استخدام `.only`، أو فلتر الاسم، أو تشغيل هذا الملف فقط). يسمي "ميسزاروس" هذا بـ _الاختبار الوحيد (Lonely Test)_.
* **تتعطل الاختبارات عند خلط الترتيب، أو تجزئتها، أو تشغيلها بالتوازي**، أو بعد ترقية مشغل الاختبارات الذي يقوم بتغيير الترتيب الافتراضي.
* **حذف أو تخطي أو إعادة ترتيب** اختبار واحد يؤدي إلى فشل اختبار آخر لا يبدو أن له صلة به.
* يؤدي فشل واحد إلى حدوث **سلسلة متعاقبة (cascade)** من حالات الفشل اللاحقة (ما يسميه ميسزاروس بـ _الاختبارات المتفاعلة - Interacting Tests_؛ أو van Deursen بـ _حرب تشغيل الاختبارات - Test Run War_ عندما تتصادم المشغلات المتوازية على هيكل اختبار مشترك).
* **تكون الاختبارات غير مستقرة بشكل متقطع (flaky)** دون أي تغيير في الكود — فالاعتماد على الترتيب هو أحد أكثر الأسباب الجذرية شيوعاً للاختبارات غير المستقرة.

المؤشر الهيكلي الواضح هو وجود **حالة مشتركة قابلة للتعديل (shared mutable state)** تتم قراءتها عبر الاختبارات: متغيرات على مستوى الوحدة البرمجية/ثابتة `static`/عامة، أو دالة `beforeAll` تقوم بملء الحالة مرة واحدة، أو مورد خارجي لم يتم إعادة تعيينه (صفوف قاعدة البيانات، الملفات، الذاكرة المؤقتة، متغيرات البيئة، المؤقتات المزيفة، كائنات المحاكاة).

```js
let users = []; // حالة مشتركة على مستوى الوحدة البرمجية

test('creates a user', () => {
  users.push({ id: 1, name: 'Ada' });
  expect(users).toHaveLength(1);
});

// ينجح فقط لأن الاختبار أعلاه تم تشغيله أولاً وقام بتعديل `users`.
// قم بتشغيل هذا الاختبار بمفرده، أو اخلط الترتيب، وسيفشل الاختبار.
test('finds the created user', () => {
  expect(users.find(u => u.name === 'Ada')).toBeDefined();
});

```

## Reasons for the Problem

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

* يتم تعديل **هيكل اختبار مشترك (Shared Fixture)** (يتم إعداده مرة واحدة في `beforeAll`، أو كمتغير على مستوى الوحدة البرمجية، أو كعضو ثابت `static` في فئة، أو كقاعدة بيانات/ملف حقيقي) بواسطة الاختبارات ولا يتم إعادة تعيينه أبداً بينها، وبالتالي يرث كل اختبار مخلفات الاختبار السابق.
* الراحة والسرعة: يعيد المطورون استخدام عمليات إعداد مكلفة أو "يبنون على" بيانات الاختبار السابق لتجنب إعادة إنشائها (وهو ما يُعرف أحياناً بـ _الاختبارات المتسلسلة - Chained Tests_).
* يكون الاعتماد **عرضياً وغير مرئي** — فلا يوجد شيء في كود الاختبار يعلن "قم بتشغيلي بعد ذلك الاختبار"، وبالتالي يستمر هذا الوضع حتى يتغير الترتيب.

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

* **الموثوقية / الثقة الزائفة.** تنجح مجموعة الاختبارات بالكامل بمجرد الحظ في الترتيب. قم بإعادة الترتيب، أو التشغيل المتوازي، أو تشغيل جزء منها وستفشل الاختبارات (أو الأسوأ من ذلك، يختفي خطأ برمي حقيقي لأن اختباراً سابقاً قد ترك حالة "صحيحة" بالصدفة). تعتبر الاختبارات المعتمدة على الترتيب مصدراً رئيسياً لـ _الاختبارات غير المستقرة (flaky tests)_.
* **قابلية الصيانة.** لا يمكنك تشغيل اختبار فاشل واحد أو تصحيحه أو إعادة تشغيله بشكل مستقل — فيجب تشغيل الاختبار التمهيدي أولاً. ويؤدي إضافة اختبارات أو إزالتها أو إعادة ترتيبها إلى تأثيرات غامضة غير مباشرة (spooky action at a distance) في اختبارات أخرى.
* **المقروئية.** لا يعود الاختبار يوثق سلوكاً واحداً بمدخلات صريحة؛ بل يتطلب فهمه قراءة كل ما تم تشغيله قبله (ضيف غامض _Mystery Guest_ مخبأ في ترتيب التنفيذ).
* **يعيق التشغيل المتوازي واختيار الاختبارات.** تفترض أدوات تجزئة الاختبارات (test sharding)، والمشغلات المتوازية، وأدوات تحديد الاختبارات المتأثرة بالمرونة استقلالية الاختبارات؛ ويجعل اقتران الترتيب استخدامها غير آمن.

## Treatment

اجعل كل اختبار **مستقلاً بذاته**: يقوم بإعداد كل ما يحتاجه، والتحقق، والتنظيف، بحيث ينتج نفس النتيجة في أي ترتيب، سواء بمفرده أو ضمن مجموعة اختبارات.

1. **امنح كل اختبار هيكلاً جديداً (Fresh Fixture).** انقل الإعداد المشترك من `beforeAll` إلى `beforeEach` (أو ابنِ الهيكل داخل الاختبار نفسه)، بحيث يتم إعادة بناء الحالة لكل اختبار بدلاً من تراكمها.
2. **تخلص من الحالة المشتركة القابلة للتغيير.** لا تقم بالقراءة/الكتابة من أو إلى متغيرات على مستوى الوحدة البرمجية، أو متغيرات `static`، أو متغيرات عامة عبر الاختبارات. ابنِ الكائنات محلياً، ومرر البيانات بشكل صريح.
3. **أعد تعيين الموارد الخارجية في خطوة التفكيك (teardown).** قم بالتراجع عن تغييرات قاعدة البيانات (معاملة واحدة لكل اختبار) أو استخدم بيانات فريدة لكل اختبار؛ واحذف الملفات المؤقتة؛ واسترجع قيم متغيرات البيئة والمتغيرات العامة والمؤقتات المزيفة؛ وامسح كائنات المحاكاة (مثل `jest.clearAllMocks()` / `vi.restoreAllMocks()`، و`jest.resetModules()`). فضل التفكيك التلقائي/المضمون على التنظيف اليدوي.
4. **أثبت الاستقلالية عن طريق إضفاء العشوائية على الترتيب.** هذا هو الكاشف الحقيقي — فهو خاصية ديناميكية لا يمكن لأداة التحليل الإملائي (linter) رؤيتها:
  * Jest: `--randomize` / `randomize: true`.
  * Vitest: `sequence.shuffle` (في الإعدادات أو عبر `--sequence.shuffle`).
  * pytest: `pytest-randomly` أو `pytest-random-order`.
  * Maven Surefire: `-Dsurefire.runOrder=random`.
  وقم بتشغيل جزء من الاختبارات/اختبار واحد في بيئة CI أيضاً لتظهر _الاختبارات الوحيدة (Lonely Tests)_.
5. **إذا كان الإعداد المشترك مطلوباً حقاً** (هيكل اختبار مكلف للقراءة فقط)، فاجعله **غير قابل للتعديل (immutable)** ومشاركاً للقراءة فقط، أو استخدم مجموعة _اختبارات متسلسلة (Chained Tests)_ موثقة عمداً كملاذ أخير — وتجنب تماماً الاعتماد العرضي. يرجى ملاحظة أن قاعدة `no-hooks` في `eslint-plugin-jest`/`eslint-plugin-vitest` قد تثبط استخدام خطافات الإعداد/التفكيك التي تميل إلى تعزيز استخدام الحالة المشتركة، ولكنها لا تكتشف الاعتماد على الترتيب بحد ذاتها.

```js
// قبل — معتمد على الترتيب: يحتاج الاختبار الثاني إلى مخلفات الاختبار الأول
let users = [];
test('creates a user', () => {
  users.push({ id: 1, name: 'Ada' });
  expect(users).toHaveLength(1);
});
test('finds the created user', () => {
  expect(users.find(u => u.name === 'Ada')).toBeDefined();
});

// بعد — يمتلك كل اختبار هيكل الاختبار الخاص به
function makeRepo(seed = []) {
  return { users: [...seed] };
}

test('creates a user', () => {
  const repo = makeRepo();
  repo.users.push({ id: 1, name: 'Ada' });
  expect(repo.users).toHaveLength(1);
});

test('finds an existing user', () => {
  const repo = makeRepo([{ id: 1, name: 'Ada' }]); // يعد الشرط المسبق الخاص به
  expect(repo.users.find(u => u.name === 'Ada')).toBeDefined();
});

```
