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

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

## Signs and Symptoms

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

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

* لا يوجد `expect` أو `assert` أو `verify` في أي مكان في جسم الاختبار.
* يتم "التحقق" عن طريق استخدام `console.log` أو طباعة النتيجة والتي يفترض أن يقوم الإنسان بفحصها بالنظر.
* اسم اختبار غامض (مثل `testChainDependencies`) وجسم لا يعطي أي إشارة إلى ما يضمنه الاختبار.
* سيظل الاختبار ناجحاً حتى لو أعادت الدالة الإنتاجية قيمة خاطئة تماماً.
* الاختبارات التي تعتمد بالكامل على عبارة "لم يتم إلقاء استثناء" دون ذكر ذلك صراحة.

```js
// مشكلة (Smell): يشغل الكود، يطبع، لا يتحقق من شيء — ينجح بغض النظر عما تعيده calculate()
test('chain dependencies', () => {
  const game = Game.newGame(0, '');
  game.setOtherGoods(Building.TOOLMAKERS, 1);
  const logic = new Logic(game);

  const res = logic.calculateChainWithDependencies(Goods.TOOLS);
  console.log(res.toString()); // لا يوجد expect(...) — ما الذي يفحصه هذا الاختبار؟
});

```

يختبئ شكل شائع من هذه المشكلة خلف المحاكاة المكثفة (heavy mocking): حيث يقوم الاختبار بتوصيل كائنات المحاكاة واستدعاء الـ SUT ولكنه لا يتحقق أبداً من قيمة الإرجاع _أو_ من التفاعلات مع كائنات المحاكاة.

## Reasons for the Problem

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

* تم إنشاء هيكل اختبار مؤقت/للمهام المستقبلية (TODO) ("جعله يترجم ويعمل") ولم يتم ملء التحقق أبداً.
* تم إرسال (commit) كود تصحيح أخطاء مؤقت — مثل `console.log` أو تشغيل يدوي للتحقق — كما لو كان اختباراً حقيقياً.
* أدت عملية إعادة الهيكلة إلى حذف التحقق أو كتابته كتعليق مع ترك كود الإعداد (setup) كما هو.
* يتم الخلط بين عبارة "يعمل دون استثناء" وعبارة "يعمل بشكل صحيح". اختبارات الدخان (Smoke tests) مشروعة، ولكن هنا لا يتم إعلان _النية_ لإجراء اختبار الدخان بشكل صريح.
* وجود هياكل اختبار تم إنشاؤها تلقائياً أو بواسطة الذكاء الاصطناعي تقوم بتشغيل دالة دون التحقق من النتيجة.

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

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

إنه الصورة المعاكسة لـ [روليت التحققات (Assertion Roulette)](#): ففي تلك المشكلة توجد تحققات _أكثر من اللازم_ وغير موثقة؛ بينما لا يحتوي الاختبار المجهول (Unknown Test) على _أي تحقق_.

## Treatment

اجعل كل اختبار يصرح بما يتوقعه، واجعل عبارة "لا ينبغي إلقاء أي استثناء" خياراً صريحاً ومقصوداً.

1. **أضف تحققاً واحداً على الأقل على نتيجة ملاحظة** — مثل قيمة الإرجاع، أو الحالة الناتجة، أو خطأ تم إلقاؤه. واستبدل طباعة كود تصحيح الأخطاء بـ `expect` على تلك القيمة.
2. **إذا كانت النية الحقيقية هي "يجب ألا يتم إلقاء استثناء"، فاذكر ذلك صراحة** باستخدام `expect(() => fn()).not.toThrow()` (أو `await expect(fn()).resolves.toBeDefined()`). وبهذا يصبح اختبار الدخان موثقاً بدلاً من أن يكون عرضياً.
3. **للاختبارات القائمة على التفاعل فقط**، تحقق من الكائن المساعد (collaborator): `expect(mock).toHaveBeenCalledWith(...)`.
4. **احذف أو استخدم `skip`/`todo` للمواقع المؤقتة الميتة** بدلاً من ترك اختبار فارغ ناجح (يسجل `it.todo('handles chained deps')` الفجوة بأمانة دون تزييف التغطية).
5. **قم بتفعيل أداة كشف** (مثل `jest/expect-expect`، أو `vitest/expect-expect`، أو SonarSource S2699) في بيئة الـ CI. وإذا قمت بلف التحققات في دوال مساعدة مخصصة، فقم بتسجيلها عبر خيار `assertFunctionNames` الخاص بالقاعدة حتى لا يتم الإبلاغ عن التحققات الحقيقية.

```js
// قبل — اختبار مجهول
test('chain dependencies', () => {
  const logic = new Logic(Game.newGame(0, ''));
  const res = logic.calculateChainWithDependencies(Goods.TOOLS);
  console.log(res.toString());
});

// بعد — النية والضمان صريحان
test('resolves tools to the toolmakers workshop chain', () => {
  const logic = new Logic(Game.newGame(0, ''));
  const res = logic.calculateChainWithDependencies(Goods.TOOLS);
  expect(res).toHaveLength(1);
  expect(res[0].building).toBe(Building.TOOLMAKERS);
});

```

## Detected by

- **eslint-jest** `expect-expect` — jest/expect-expect (https://github.com/jest-community/eslint-plugin-jest/blob/main/docs/rules/expect-expect.md)
- **eslint-vitest** `expect-expect` — vitest/expect-expect (https://github.com/vitest-dev/eslint-plugin-vitest/blob/main/docs/rules/expect-expect.md)
- **sonar** `S2699` — يجب أن تتضمن الاختبارات عمليات تحقق (JavaScript) (https://rules.sonarsource.com/javascript/RSPEC-2699/)
- **sonar** `S2699` — يجب أن تتضمن الاختبارات عمليات تحقق (Java) (https://rules.sonarsource.com/java/RSPEC-2699/)
