Jannah Theme License is not validated, Go to the theme options page to validate the license, You need a single license for each domain name.

تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة

كيف أستخدم آلاف الاختبارات المُولَّدة لاكتشاف الأخطاء المخفية في الكود الخاص بي

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

concept-of-computer-programming-or-developing-software-laptop-computer-with-code-on-screen-heart-message-cog-home-user-cloud-and-lock-icons تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة

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

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

هل سبق لك أن رأيتَ ظهور “NaN” على موقع ويب ذي ترميز سيء؟ أو حيرتَ نفسك بسبب خطأ برمجي مستمر، لتكتشف لاحقًا أنه مجرد خلل في اللغة؟ تكمن المشكلة جزئيًا في افتراضاتك، وربما في حزمة اختبارات غير مكتملة. سأشرح ما هو الاختبار القائم على الخصائص (PBT) وكيف يحل هذه المشاكل.

ما هو الاختبار القائم على الخصائص (PBT)؟

على مستوى عالٍ جدًا، يُدخل آلاف القيم العشوائية في الاختبارات، مما يُنشئ آلاف الاختبارات في هذه العملية. يُقارن الاختبار القائم على الخصائص (PBT) الاختبار القائم على الأمثلة، الذي يُقارن النتائج ببعض القيم المعروفة.

لنلقِ نظرة على “الاختبار القائم على الأمثلة” باستخدام جافا سكريبت:

// Node.js
test("adds two numbers", () => {
  assert(add(1, 2) == 3);
});

النهج البسيط هو افتراض أن حالة الاختبار هذه تغطي كل شيء. ولكن لكي نكون أكثر شمولاً، يمكننا أيضًا اختبار الأعداد السالبة، والأصفار، والأعداد العشرية، وأصغر وأكبر عددين صحيحين آمنين (Number.MIN_SAFE_INTEGER وNumber.MAX_SAFE_INTEGER)، والقيم خارج هذا النطاق. للحصول على شيفرة برمجية متينة، يمكننا حتى استخدام أي نوع بيانات يمكن تصوره والتحقق من أنه يتعامل معها بسلاسة عن طريق طرح خطأ. وهذا عبء يستغرق وقتًا طويلاً.

بعد كتابة عشرات الاختبارات، قد تتوصل إلى شيء مثل هذا:

function add(a, b) {
  if (typeof a !== "number") throw Error();
  if (typeof b !== "number") throw Error();
  return a + b;
}

يبدو هذا معقولاً، أليس كذلك؟ باستثناء:

typeof NaN === "number"; // -> true

لذا، سيُسرّع ذلك. لنُحسّنه.

function add(a, b) {
  if (typeof a !== "number") throw Error();
  if (typeof b !== "number") throw Error();
  if (a === NaN) throw Error();
  if (b === NaN) throw Error();
  return a + b;
}

بالتأكيد الآن…

add(NaN, 1);
text-in-a-terminal-window-shows-the-output-of-the-add-function-it-s-simply-nan تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة

اقرأ أيضا:  كيفية إعداد رسالة مباشرة جماعية في Discord

أنا شخصيًا مصدوم. إذا لم تكن تعلم، فإن NaN لا يساوي نفسه، لذا فإن “a === NaN” دائمًا ما تكون خاطئة. هذه الأمور شائعة في جافا سكريبت لأنها لغة سيئة التخطيط. ومع ذلك، فإن افتراضاتنا هي التي أوقعتنا في المشاكل. من الصعب تخيل جميع الحالات الحدية، ولو كان هذا الكود متاحًا للعامة، فمن يدري ما الضرر الذي قد يسببه؟

يُلقي الاختبار القائم على الخصائص بكل شيء، بما في ذلك حوض المطبخ، على الكود الخاص بك. فبدلًا من بضعة أمثلة، يُولّد آلاف الأمثلة ويغطي نطاق الإدخال بأكمله. يكشف عن العديد من الافتراضات ويضمن متانة الكود الخاص بك.

كيف يعمل PBT؟

هناك ثلاثة عناصر رئيسية لـ PBT:

  • الخصائص: الجوانب المرغوبة أو غير المرغوبة في نظامك – الشيء الذي تختبره.
  • المولدات: الدوال المسؤولة عن توليد كميات كبيرة من المعلمات.
  • التقليص: ما ستفعله مكتبة PBT بالمعلمات الفاشلة التي تعثر عليها (سنتناوله لاحقًا).

الخصائص

يؤدي البحث عن PBT على الإنترنت بسرعة إلى دخولك في عالم المصطلحات الأكاديمية. تأتي الخصائص بأشكال مختلفة، وكثير منها غير مناسب للمبتدئين (ولا للاستخدام البشري). إليك بعض الأمثلة البسيطة والشائعة:

  • الثبات: جودة العملية التي تُنتج نفس النتيجة عند تنفيذها عدة مرات.
  • الثوابت: أشياء صحيحة دائمًا – مثل رطوبة الماء.
  • قيود النطاق: نطاقات القيم الصحيحة/غير الصحيحة.

هذه بعض الأمثلة. يوجد المزيد، لكنك تفهم الفكرة. بعضها يصعب فهمه، لذا كن حذرًا.

نصيحة
يُركز PBT بشكل مفرط على اختبار “الخصائص”، بينما في الواقع، حتى الاختبارات القائمة على الأمثلة تستهدف خصائص النظام. لا تُكثر من التفكير في هذا الجزء. تعامل مع PBT كاختبارات وحدات توليدية. يمكنك التعرّف على الخصائص مع تقدمك.

المولدات

تأتي مكتبات PBT مع واجهة برمجة تطبيقات شاملة لتوليد أي قيمة مُتصورة. وهي تتكون من دوال توليد قابلة للتركيب، ومرنة بما يكفي للدمج بأي طريقة، لإنتاج أي بنية بيانات مُعقدة ترغب بها.

التقليص

قد تكون البيانات المُولَّدة مُعقَّدة ويصعب فهمها. لتحسين الفهم، يستخدم PBT التقليص. التقليص عملية بسيطة. عند العثور على قيمة مُعطَّلة، يتم تقليصها وإعادة اختبارها مرارًا وتكرارًا. تستمر هذه العملية حتى العثور على أصغر قيمة مُعطَّلة. على سبيل المثال، تقليص عدد من مليارات إلى 0، أو قائمة ضخمة إلى قائمة فارغة. تبحث عملية التقليص عن أبسط حالة مُعطَّلة، مما يجعلها أكثر منطقية.

اقرأ أيضا:  أفضل 7 طرق لإصلاح مشكلة Google Drive في عدم حذف الملفات

كيف يبدو PBT؟

بما أنني بدأتُ باستخدام JavaScript، فسأستمر في استخدامه – fast-check مكتبة PBT رائعة، وسأستخدمها في الأمثلة التالية.

لنبدأ بدالة “إضافة” بسيطة ونُحسِّنها تدريجيًا.

function add(a, b) {
  return a + b;
}

الآن دعونا نقوم بإنشاء ملف الاختبار الخاص بنا:

const fc = require("fast-check");
const { add } = require("./add");

test("adds two numbers", () => {
  fc.assert(
    fc.property(fc.integer(), fc.integer(), (a, b) => {
      const result = add(a, b);
      return result === a + b;
    }),
  );
});
text-in-a-terminal-window-indicates-that-a-test-passes تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة
  • “fc.assert”: يُشغّل اختبارات خصائص متعددة ويُقلّص حالات الفشل.
  • “fc.property”: تُعرّف مُعاملاته (المولدات) الخاصية التي تُريد اختبارها.
  • “fc.integer”: مُولّد يُنتج عددًا صحيحًا عشوائيًا.
الشكل العام لدالة “fc.property” هو:
// "fc.assert()" runs this function many times.
// Each generator produces one random value per execution.
fc.property(
  ...arbitraries // Aka generators. Functions that produce a random value.
  (...args) => {
      // A predicate function. Return true to pass and false to fail.
      // You can alternatively use expect() here.
      // The generators inject a random value into each "arg."
  };
);
دعونا الآن نلقي بيانات (كائنات) غير متوقعة على وظيفتنا ونرى ما إذا كان ذلك سيؤدي إلى حدوث خطأ.
test("throws for objects", () => {
  fc.assert(
    fc.property(fc.integer(), fc.object(), (a, b) => {
      expect(() => add(a, b)).toThrow();
      return true; // Otherwise we return undefined, which is falsy.
    }),
  );
});
text-in-a-terminal-window-indicates-that-one-test-passes-but-another-one-fails-annotations-on-the-screen-show-that-the-failing-parameter-was-an-object تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة
توقعنا ظهور خطأ عند استقبال الكائنات، لكنه لم يحدث. لنُحدّث الدالة.
function add(a, b) {
  if (typeof a !== "number") throw Error();
  if (typeof b !== "number") throw Error();
  return a + b;
}
text-in-a-terminal-window-indicates-that-all-four-tests-pass تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة
ومع ذلك، دعونا نلقي كل شيء عليه، بما في ذلك حوض المطبخ (NaN).
// A simple function that returns our generator.
function kitchenSink() {
  // The generated value will be one of NaN or anything (except a number).
  return fc.oneof(
    fc.constant(NaN),
    // Everything except numbers.
    fc.anything().filter((t) => typeof t !== "number"),
  );
}

test("everything and the kitchen sink", () => {
  fc.assert(
    fc.property(kitchenSink(), kitchenSink(), (a, b) => {
      expect(() => add(a, b)).toThrow();
      return true;
    }),
  );
});
text-in-a-terminal-window-indicates-that-two-tests-pass-but-one-fails-there-are-annotations-on-the-screen-that-indicate-after-5-tests-an-nan-value-caused-it-to-fail تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة
لا تُصدر دالتنا خطأً لـ NaN، لأن NaN هو في الواقع رقم. هذا هو نوع الافتراض الذي يُوقعنا عادةً في الخطأ. لنُعالج المشكلة ونختبرها.
function add(a, b) {
  if (typeof a !== "number") throw Error();
  if (typeof b !== "number") throw Error();
  if (isNaN(a)) throw Error();
  if (isNaN(b)) throw Error();
  return a + b;
}
text-in-a-terminal-window-indicates-that-all-three-tests-pass تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة
الآن، اختبار آخر للتأكد من أن وظيفتنا إما ترجع رقمًا صالحًا أو ترمي خطأً.
test("always returns a number or throws", () => {
  fc.assert(
    fc.property(fc.anything(), fc.anything(), (a, b) => {
      try {
        // Number.isFinite() returns false for all non-numbers.
        // Returning false fails the test.
        // Therefore, this test fails if the "add" returns a non-number.
        return Number.isFinite(add(a, b));
      } catch (e) {
        // We expect "add" to throw an error; it's receiving garbage.
        return true;
      }
    }),
    // I've set 10,000 tests (up from the default 100).
    // More tests mean more chance of catching subtle bugs.
    { numRuns: 10000 },
  );
});

text-in-a-terminal-window-indicates-that-three-tests-pass-but-one-fails-there-are-annotations-on-the-screen-that-indicate-after-2000-tests-an-infinity-value-caused-it-to-fail تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة

هذا أمر جيد. هذا يعني أننا نكتشف الأخطاء الآن، بدلاً من ملء صفحات الويب ببيانات غير متوفرة. تغيير أخير.

اقرأ أيضا:  كيف يمكن أن تجعل نتائج البحث Google Perspectives أكثر دقة

ملاحظة
نظرًا لأن PBT يُلقي ببيانات عشوائية على الكود، فإنه لا يكشف عن أي مشاكل في كل مرة يُجرى فيها اختبار. قد يكون اكتشاف الأخطاء متقطعًا، وزيادة عدد مرات إجراء الاختبار يُحسّن من اختبار الكود.

function add(a, b) {
  if (!Number.isFinite(a)) throw new Error();
  if (!Number.isFinite(b)) throw new Error();
  return a + b;
}
text-in-a-terminal-window-indicates-that-all-four-tests-pass تقنية فعّالة لاكتشاف الأخطاء الخفية في الكود باستخدام الاختبارات التلقائية الواسعة
ممتاز. ربما يكون هذا الكود الأكثر هندسةً في التاريخ، لكنني الآن واثقٌ من أنه لن يفشل فجأةً عند حدوث خطأ ما. إنه يفشل بالضبط في المكان المفترض أن يفشل فيه.

إذا كنتَ عاقلاً، فستتوقع أن يعمل الكود البرمجي العاقل، لكن جافا سكريبت مُختلّة. حتى بايثون قد تحتوي على عيوب غير متوقعة. بالإضافة إلى ذلك، قد تُزيل اللغات ذات الكتابة القوية فئة كاملة من الأخطاء البرمجية، لكنها لا تُزيل أخطاء البرمجة، أو الأخطاء في المواصفات، أو الافتراضات السخيفة. إذا كنتَ ترغب في كود برمجي قوي، فعليك إخضاعه لاختبارات مكثفة، والاختبار القائم على الأمثلة لا يكفي.

توفر بايثون مكتبة PBT رائعة تُسمى Hypothesis، وتُوفر Golang مكتبة Rapid. إذا كنتَ تكتب كودًا برمجيًا عامًا أو تستخدم لغات برمجة غير موثوقة، فإنني أوصي بشدة باستخدام PBT.

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

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

زر الذهاب إلى الأعلى