JUnit اختبار المعلمات مع مثال باستخدام @Parameters

⚡ ملخص ذكي

الاختبارات ذات المعلمات في JUnit يتم تشغيل نفس طريقة الاختبار بشكل متكرر مع قيم إدخال مختلفة، بحيث تغطي طريقة واحدة العديد من السيناريوهات. توفر التعليقات التوضيحية @RunWith و @Parameters مجموعة البيانات لكل تكرار.

  • 🔘 مصدر البيانات: تقوم الدالة الثابتة @Parameters بإرجاع مجموعة من المصفوفات، وتصبح كل مصفوفة بمثابة تكرار اختبار واحد.
  • ☑️ عداء: يستبدل @RunWith(Parameterized.class) الكتلة الافتراضيةJUnit4ClassRunner ويعيد بناء الفئة مرة واحدة لكل صف بيانات.
  • ✅ البناء: يقوم مُنشئ عام واحد بتخزين صف واحد من البيانات في حقول المثيل التي يتحقق منها أسلوب الاختبار.
  • 🧪 مثال عملي: تُستخدم خمسة صفوف إدخال في اختبار sum()، و JUnit عرض التقارير: تم التشغيل بنجاح 5/5 بدون أي أعطال.
  • 🛠️ JUnit 5: يؤدي استخدام @ParameterizedTest مع @ValueSource أو @CsvSource أو @MethodSource إلى إزالة كل من المُشغِّل والمنشئ.
  • 📌 المزالق: يؤدي وجود طريقة غير ثابتة @Parameters، أو وجود منشئين عامين، أو وجود تبعية junit-jupiter-params مفقودة إلى إيقاف التشغيل.

JUnit اختبار مُعَلم باستخدام التعليقات التوضيحية @RunWith و @Parameters

ما هو الاختبار المُعَلم في JUnit?

A اختبار مُعَلم هو اختبار يُنفّذ نفس أسلوب الاختبار مرارًا وتكرارًا باستخدام قيم مختلفة. يساعد هذا الاختبار المطورين على توفير الوقت عند كتابة اختبارات تختلف فقط في مدخلاتها ونتائجها المتوقعة.

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

لنفترض اختبارًا بسيطًا يجمع أرقامًا مختلفة. قد يبدو الكود كالتالي:

JUnit طريقة الاختبار التي تكرر ثلاث استدعاءات assertEquals لطريقة الجمع

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

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

خطوات إنشاء معلمة JUnit اختبار

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

الخطوة 1) أنشئ فئة. في هذا المثال، سنقوم بإدخال رقمين باستخدام دالة sum(int, int)، والتي ستعيد مجموع الرقمين المُدخلين.

فئة Airthematic تُعلن عن دالة جمع عامة تجمع وسيطين من نوع عدد صحيح

الخطوة 2) أنشئ فئة اختبار ذات معلمات.

رأس فئة الاختبار مُعلَّم بـ @RunWith(Parameterized.class) وأربعة حقول خاصة

Code تفسير

  • Code خط 11: قم بتعليق فئة الاختبار الخاصة بك باستخدام @RunWith(Parameterized.class).
  • Code خط 13: الإعلان عن المتغير "firstNumber" على أنه خاص واكتبه كـ int.
  • Code خط 14: تعريف المتغير 'secondNumber' كمتغير خاص ونوعه int.
  • Code خط 15: تعريف المتغير 'expectedResult' كمتغير خاص ونوعه int.
  • Code خط 16: تعريف المتغير 'airthematic' كمتغير خاص ونوعه كـ Airthematic.

@إركض مع(اسم_الفئة.الفئة): الـ @إركض مع تُستخدم التعليقات التوضيحية لتحديد اسم فئة المُشغِّل. إذا لم نُحدد أي نوع كمعامل، فسيختار وقت التشغيل حظرJUnitعداء من الفئة الرابعة بشكل افتراضي.

تتولى هذه الفئة مسؤولية تشغيل الاختبارات باستخدام نسخة اختبار جديدة. وهي مسؤولة عن استدعاء JUnit أساليب دورة الحياة مثل الإعداد (ربط الموارد) والإنهاء (تحرير الموارد)، والتي تم وصفها في JUnit تثبيت إختبار البرنامج التعليمي.

لتعيين المعلمات، تحتاج إلى إضافة تعليق توضيحي للفئة باستخدام @RunWith وتمرير .class المطلوب لاختباره.

الخطوة 3) أنشئ دالة إنشاء تقوم بتخزين بيانات الاختبار. تقوم هذه الدالة بتخزين 3 متغيرات.

مُنشئ اختبار مُعامل يُسند ثلاثة وسائط عددية صحيحة إلى حقول الكائن

الخطوة 4) قم بإنشاء طريقة ثابتة تقوم بإنشاء وإرجاع بيانات الاختبار.

دالة إدخال ثابتة مُعَلَّمة بـ @Parameterized.Parameters تُعيد مصفوفة كائنات ثنائية الأبعاد

Code خط 32,33: إنشاء مصفوفة ثنائية الأبعاد (مع توفير معلمات الإدخال للجمع). باستخدام دالة asList، نقوم بتحويل البيانات إلى نوع قائمة، لأن نوع الإرجاع لدالة الإدخال هو مجموعة.

Code خط 30: باستخدام @حدود تعليق توضيحي لإنشاء مجموعة من بيانات الإدخال لإجراء اختبارنا.

تُعيد الدالة الثابتة المُحددة بواسطة التعليق التوضيحي @Parameters مجموعةً، حيث يُمثل كل عنصر فيها بيانات الإدخال لتكرار واحد من الاختبار. لنفترض العنصر {1,2,3}. هنا:

  • الرقم الأول = 1
  • الرقم الثاني = 2
  • النتيجة المتوقعة = 3

هنا، سيتم تمرير كل عنصر من عناصر المصفوفة إلى الدالة البانية، عنصرًا تلو الآخر، نظرًا لإنشاء نسخ متعددة من الصنف. وبالتالي، فإن المصفوفات الخمس المُعلنة في المثال تُنتج عمليات التشغيل الخمس التالية:

تكرار الرقم الأول الرقم الثاني نتيجة متوقعة خط الكونسول
[0] 1 2 3 مجموع Numbers = : 3
[1] 11 22 33 مجموع Numbers = : 33
[2] 111 222 333 مجموع Numbers = : 333
[3] 10 9 19 مجموع Numbers = : 19
[4] 100 9 109 مجموع Numbers = : 109

الخطوة 5) الكود الكامل.

قائمة اختبار AirthematicTest كاملة مع عمليات الاستيراد، والدالة البانية، ودالة @Parameters، ودالة @Test

Code التفسير:

  • Code خط 25: يُستخدم التعليق التوضيحي @Before لإعداد الموارد (Airthematic.class هنا). يُستخدم التعليق التوضيحي @Before هنا لتشغيله قبل كل حالة اختبار، ويحتوي على الشروط المسبقة للاختبار.
  • Code خط 36: استخدام التعليق التوضيحي @Test لإنشاء اختبارنا.
  • Code خط 39: خلق بيان التأكيد للتحقق مما إذا كان مجموعنا مساوياً لما توقعناه.

الخطوة 6) أنشئ فئة تشغيل اختبار لتشغيل الاختبار ذي المعلمات:

فئة TestRunner التي تمرر AirthematicTest.class إلى JUnitCore.runClasses وطباعة حالات الفشل

Code التفسير:

  • Code خط 8: تعريف الدالة الرئيسية للفئة Test التي ستشغل برنامجنا JUnit الاختبار.
  • Code خط 9: تنفيذ حالات الاختبار باستخدام JUnitCore.runClasses، الذي يأخذ اسم فئة الاختبار كمعامل (في مثالنا نستخدم AirthematicTest.class).
  • Code خط 11: معالجة النتيجة باستخدام حلقة تكرارية وطباعة النتيجة الفاشلة.
  • Code خط 13: طباعة النتيجة الناجحة.

الإخراج:

هذه هي النتيجة، والتي تُظهر اختبارًا ناجحًا بدون أي فشل. tracهـ، كما هو موضح أدناه. لاحظ أن JUnit يعرض العرض إدخالًا واحدًا لكل صف بيانات بدلاً من اختبار واحد:

Eclipse JUnit عرض التقارير: تم تشغيل البرنامج بنجاح 5/5 بدون أخطاء أو حالات فشل للفئة المُعَلمة

انظر إلى النتيجة على الشاشة، والتي تُظهر مجموع الرقمين:

Eclipse طباعة وحدة التحكم مجموع واحد Numbers سطر لكل صف من صفوف المعلمات الخمسة

الاختبارات المعيارية في JUnit 5 مع @ParameterizedTest

المثال أعلاه مكتوب لـ JUnit 4. JUnit الإصدار 5 (جوبيتر) يُلغي نموذج المُشغّل تمامًا، لذا تختفي الدالة @RunWith(Parameterized.class) ومنشئ البيانات وحقول الكائن. JUnit الكود رقم 4 الموضح أعلاه ليس قديمًا: لا يزال يعمل دون تغيير على JUnit يتم استخدام المنصة عبر المحرك القديم. ومع ذلك، عادةً ما تُكتب الاختبارات الجديدة باستخدام @ParameterizedTest.

يلزم وجود تبعيتين: junit-jupiter-api لتعليقات الاختبار و معلمات junit-jupiter بالنسبة للدعم المُعَلم. بدون العنصر الثاني، لن يتم حل التعليقات التوضيحية للمصدر على الإطلاق.

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class AirthematicTest {

    // one row per iteration, no constructor and no runner
    @ParameterizedTest(name = "{0} + {1} = {2}")
    @CsvSource({"1, 2, 3", "11, 22, 33", "111, 222, 333", "10, 9, 19", "100, 9, 109"})
    void sumOfTwoNumbers(int firstNumber, int secondNumber, int expectedResult) {
        assertEquals(expectedResult, new Airthematic().sum(firstNumber, secondNumber));
    }
}

يقدم كوكب المشتري مصادر متعددة للحجج، ويعتمد المصدر الصحيح على شكل البيانات:

شرح المصدر لوازم استخدمه عندما
@ValueSource عمود واحد من القيم الحرفية يأخذ الاختبار وسيطًا واحدًا فقط
@CsvSource صفوف مفصولة بفواصل مضمنة تُقرأ الجداول الصغيرة من الأرقام والسلاسل النصية بشكل واضح في الملف
@CsvFileSource يتم قراءة الصفوف من ملف CSV الموجود في مسار فئة الاختبار مجموعة البيانات كبيرة أو يتم الاحتفاظ بها خارج الكود
@MethodSource مصنع ثابت يُعيد سلسلة من الوسائط هناك حاجة إلى كائنات حقيقية أو قيم محسوبة أو بيانات عشوائية
@EnumSource ثوابت التعداد يجب اختبار كل قيمة من قيم التعداد

هناك قاعدتان تُربكان معظم المبتدئين. أولاً، يتم تجاهل أي تعليق توضيحي للمصدر مُضاف إلى دالة @Test عادية، لذا يجب أن تحمل الدالة @ParameterizedTest. ثانياً، تُقرأ القيمة الفارغة غير المُقتبسة في @CsvSource على أنها قيمة فارغة (null)، بينما تُقرأ القيمة الفارغة المُقتبسة على أنها سلسلة نصية فارغة.

استخدم JUnit تُطبَّق أربع تعليقات توضيحية مستخدمة في هذه المقالة على Jupiter كما يلي: @RunWith(Parameterized.class) تُصبح @ParameterizedTest بالإضافة إلى تعليق توضيحي للمصدر، و@Parameters تُصبح @MethodSource أو @CsvSource، و@Before تُصبح @BeforeEach. تُغطَّى القائمة الكاملة في JUnit الشروح البرنامج التعليمي.

مزايا وقيود الاختبارات المعيارية

لا يُعدّ استخدام المعلمات مجانيًا. فهو يزيل التكرار، ولكنه يقيّد أيضًا كيفية كتابة الاختبار، لذا يجدر معرفة كلا الجانبين قبل تحويل مجموعة اختبارات موجودة.

المزايا

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

القيود

  • أحد أشكال التأكيد: كل صف يقوم بتشغيل نفس التأكيدات، لذلك فإن السيناريو الذي يحتاج إلى عمليات تحقق مختلفة لا يزال بحاجة إلى طريقة اختبار خاصة به.
  • نطاق على مستوى الفصل الدراسي في JUnit 4: يقوم المُشغِّل بتعريف الفئة بأكملها، لذا فإن طرق @Test غير ذات الصلة في تلك الفئة تعمل أيضًا مرة واحدة لكل صف.
  • تقارير غير قابلة للقراءة: بدون قالب اسم، تظهر حالات الفشل على أنها testAirthematicTest[3]، وهو ما لا يقول شيئًا عن البيانات التي تعطلت.
  • بيانات مضمنة ضخمة: تتسبب المصفوفات الكبيرة في إعاقة منطق الاختبار؛ انقلها إلى @CsvFileSource أو إلى مصنع @MethodSource بدلاً من ذلك.

الأخطاء الشائعة في JUnit الاختبارات ذات المعلمات

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

رسالتك سبب حل
يجب أن يحتوي صنف الاختبار على مُنشئ عام واحد فقط لا يُعلن الصنف عن أي مُنشئ عام، أو اثنين منهما. احتفظ بدالة إنشاء عامة واحدة تتطابق معلماتها مع أعمدة البيانات
لا توجد طريقة معلمات ثابتة عامة في الفئة الدالة @Parameters ليست عامة ثابتة، أو تُرجع نوعًا خاطئًا قم بتعريفه كمجموعة عامة ثابتة وقم بإرجاع Arrays.asList(…)
استثناء IllegalArgumentException: عدد الوسائط غير صحيح يكون الصف أعرض أو أضيق من قائمة معلمات المُنشئ اجعل عرض كل مصفوفة في المجموعة مساويًا لعرض المنشئ.
خطأ في التكوين: لا يوجد موفر وسائط يحمل اختبار جوبيتر @ParameterizedTest بدون تعليق توضيحي للمصدر أضف @ValueSource أو @CsvSource أو @CsvFileSource أو @MethodSource أو @EnumSource
يبدو أن شرح المصدر لا يفعل شيئًا. تمت إضافة التعليق التوضيحي @Test إلى الطريقة بدلاً من @ParameterizedTest استبدل @Test بـ @ParameterizedTest واستورد junit-jupiter-params

ومن بين الفخاخ الأخرى حالة البيانات المشتركة. لأن JUnit يُنشئ نسخة جديدة لكل صف، وتظل جميع البيانات المخزنة في الحقول الثابتة محفوظة في كل تكرار، ويمكن للقيمة المكتوبة بواسطة الصف [0] أن تُغير نتيجة الصف [4] دون أن تُلاحظ. احتفظ بحالة كل صف في حقول النسخة، وأعد ضبط الموارد المشتركة في الدالة @Before أو @BeforeEach. تُغطى الإرشادات العامة حول عزل الاختبارات في... وحدة التجارب البرنامج التعليمي.

الأسئلة الشائعة

TestNG يقوم بتوفير الصفوف من خلال طريقة @DataProvider المشار إليها لكل طريقة اختبار، لذلك فإن الاختبارات غير ذات الصلة في الفئة لا تتأثر. JUnit 4- يقوم بتعريف الفئة بأكملها من خلال برنامج التشغيل الخاص بها. JUnit 5 يسد تلك الفجوة باستخدام @ParameterizedTest لكل طريقة.

نعم. JUnit يقبل الإصدار 4 الدالة @Parameters(name = “{index}: sum({0},{1})={2}”)، بينما يقبل الإصدار Jupiter الدالة @ParameterizedTest(name = “…”). يتم استبدال العناصر النائبة أثناء التشغيل، لذا يُشير تقرير الخطأ إلى الصف المُسبب للمشكلة بدلاً من عرض فهرس فارغ.

نعم. JUnit يدعم الإصدار 4 استخدام @Parameter(0) و @Parameter(1) على الحقول العامة غير الثابتة، ويعتمد الصنف حينها على المُنشئ الافتراضي. يؤدي الجمع بين حقن الحقول ومنشئ البيانات إلى ظهور خطأ "منشئ عام واحد فقط".

JUnit 4 لا يحتاج إلا إلى قطعة junit، لأن العداء المُعامل يأتي بداخلها. JUnit 5 يحتاج إلى junit-jupiter-params إلى جانب junit-jupiter-api؛ بدون هذا العنصر، يفشل @ParameterizedTest وكل تعليق توضيحي للمصدر في الحل.

يوفر Jupiter @CsvFileSource(resources = “/data.csv”, numLinesToSkip = 1 )، والذي يقرأ الصفوف من مسار فئة الاختبار. JUnit لا يوجد مكافئ مدمج للرقم 4، لذلك يجب على طريقة @Parameters فتح الملف وتحليله بنفسها قبل إرجاع المجموعة.

In JUnit ٤- يمكن ذلك، لكن المُشغِّل يُعَدِّل الفئة بأكملها، لذا يتم تشغيل كل دالة مرة واحدة لكل صف بيانات. أما جوبيتر فيُعَدِّل الدوال الفردية، لذا فإن دوال @Test العادية في نفس الفئة لا تزال تُنفَّذ مرة واحدة بالضبط.

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

مساعد الطيار جيثب يتم إنتاج السقالات بسرعة ولكن غالباً ما يتم خلطها. JUnit يستورد الإصدار 4 و Jupiter، ويترك أحيانًا تعليقًا توضيحيًا للمصدر على دالة @Test عادية. تحقق من عمليات الاستيراد قبل تشغيل مجموعة الاختبارات.

تلخيص هذه التدوينة بـ: