استخدام Parameterized و Theories في الـ JUnit

شرح طريقة استخدام الـ Parameterized و Theories في الـ JUnit للتحقق من المدخلات المتعددة للدوال

Mohammad Laifمنذ 6 سنوات

 

بسم الله الرحمن الرحيم
السلام عليكم ورحمة الله وبركاته

في هذه المقالة سنتعلم كيفية استخدام الـ Parameterized (الدوال متعددة المدخلات) و Theories (النظريات وافتراضاتهم)  للتحقق من مزيج للمدخلات المتعددة في دوال الاختبار, حتى لايصادف المشروع اي من الاخطاء المستقبليه بسبب مدخلات عشوائيه او شاذة للدوال. وبما انهم متشابهان في الوظيفة فأرى من الافضل التطرق لهم في مقالة واحدة.

 

المواضيع السابقة (مهمة لفهم هذا المقال)

هذه هي المقالات السابقة والتي تعتبر ضروريه لفهم هذا المقال فاذا لم تكن لديك خلفيه مع الـ JUnit انصحك بقرائتها قبل هذا الموضوع والذي يعتبر المقال السادس من اصل ستة مقالات والاخير في هذه السلسلة للإصدار الرابع من JUnit والذي يتوافق مع تطوير تطبيقات الاندرويد, اما بالنسبه للإصدار الخامس من الـ JUnit فهو مشابه للإصدار الرابع هذا مع اضافة بعض من الاشياء الجديده فقط.

https://3alam.pro/articles/android/junit-in-java/

 

https://3alam.pro/articles/java/junit-in-java-part2/

 

https://3alam.pro/articles/java/junit-junitmatchers/

 

https://3alam.pro/articles/java/junit-rules/

 

https://3alam.pro/articles/java/junit-suites-categories/

 

رابط المشروع

https://github.com/mzdhr/KidCalculator-JUnit

وتوجد كلاسات هذه المقالة باسم: ParameterizedTest.java و TheoriesTest.java في المشروع. واذا اردت الاطلاع على كلاس الـموديل فهذه هي Calculator.java.

 

ماذا سوف تقرئ في هذه المقالة

ماهو المقصود بالمدخلات والمخرجات للدالة؟ ماهي المعضلات التي تقوم الـ Parameterized و Theories بحلهم؟ كيفية استخدامهم؟ وماهي خطوات كتابة كلاساتهم؟ وماهو الفرق بينهم؟ وايضاً ماهي الفروقات بين دالة النظرية ودالة الاختبار.

 

توضيح ماهي المدخلات للدالة؟

هي الـ Parameters للدالة.

مثال للدالة


    public int addition(int firstNumber, int secondNumber) {
        return firstNumber + secondNumber;
    }

مثال للمدخلات


addition(3, 5);

تعقيب

الرقم ٣ والرقم ٥ يعتبران مدخلات للدالة. والناتج يعتبر المخرج لها.

 

بعض من المعضلات التي تقوم الـ Parameterized و الـ Theories بحلهم

هذه بعض من ابرز المشاكل او المعضلات التي تستطيع حلهم باستخدام الـ Parameterized و الـ Theories في الـ JUnit.

  • ماذا لو اردت ان تقوم بكتابة اختبار لدالة الـ addition يحتوي على الكثير من المدخلات؟ هنا نستخدم الـ Parameterized.
  • ماذا لو اردت التحقق من ان الناتج دائماً صحيح كما تريده لداله ما, حتى وان قمنا بشقلبة او مزج او خلط او عكس المدخلات مع بعضها البعض؟ هنا نستخدم الـ Theories.

 

استخدام Parameterized في الـ JUnit

نستخدم الـ Parameterized اذا اردنا اجراء اختبارات كثيرة على دالة ما, وهذه الاختبارات تختلف في المدخلات و المخرجات للدالة.

 

معضله

تخيل لو ان لديك الدالة التالية:


    public int addition(int firstNumber, int secondNumber) {
        return firstNumber + secondNumber;
    }

وانك تريد ان تقوم بكتابة دالة اختبار تقوم بتجربة العديد من المدخلات عليها (ربما stream كامل من المدخلات وليس فقط لسته) لإختبار صحة منطقها. فالحل البدائي هو تكرار كتابة هذه الدالة بعدد المدخلات التي تريد تجربتهم عليها. اما الحل المناسب هو كتابة كلاس Parameterized تقوم بهذا العمل على اكمل وجهه.

 

خطوات كتابة كلاس Parameterized لإختبار المدخلات و المخرجات المتعددة

  1. نقوم بعمل كلاس مشغل للـ Parameterized وذلك من خلال النوتيشن RunWith كما في حال المشغلات بالمقالة السابقة.
  2. نقوم بكتابة الـ Fields التي سوف نحتاجها في هذه الكلاس مثل عنصر من الموديل Calculator.java وايضاً عناصر ترمز للمدخلات والمخرجات.
  3. نكتب دالة setUp ذي النوتيشن Before, لتجهيز عناصرنا المراد اجراء الاختبارات عليهم.
  4. ننشئ Construction لربط عناصر الـ Fields بالقيم التي سوف ندخلها لاحقاً.
  5. ننشئ دالة data ونجعلها static تقوم بإرجاع المدخلات و المخرجات التي نريد اختبار الدالة بها, ونضع النوتيشن Parameterized.Parameters فوقها.
  6. الان نقوم بكتابة دالة الاختبار, والتي سوف تختبر الدالة addition من الموديل Calculator. ونستخدم القيم التي بالـ Fields كمدخلات و مخرجات لدالة الـ addtion.

الكود لكلاس مشغل الـ Parameterized


package com.company;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.assertEquals;

// This class become a Parameterized Runner
@RunWith(Parameterized.class)
public class ParameterizedTest {

    // Fields
    private Calculator mCalculator;
    private int mInput01;   // uses to store input for the first number.
    private int mInput02;   // uses to store input for the second number.
    private int mExpect;    // uses to store input for the expect test result.

    @Before
    public void setUp(){
        // Arrange
        mCalculator = new Calculator("Orange");
    }

    // Class Constructor -> To hook the values to our @Test Method.
    // It will run for each parameter we have in our Data.
    public ParameterizedTest(int input01, int input02, int expected){
        mInput01 = input01;
        mInput02 = input02;
        mExpect = expected;
    }

    // This notation provide the input parameters for our @Test Method via the Constructor.
    @Parameterized.Parameters
    public static List<Object[]> data(){
        return Arrays.asList(new Object[][]{
                {2, 4, 6},          // means: 2 + 4 = 6
                {4, 4, 8},          // means: 4 + 4 = 8
                {-11, 4, -7},       // means: -11 + 4 = -7
                {66, 395, 461},     // means: 66 + 395 = 461
                {1000, -4, 996},    // means: 1000 + 4 = 996
                {99, 1, 100}        // means: 99 + 1 = 100
        });
    }

    // Our @Test Method
    @Test
    public void additions_test(){
        // Action
        int result = mCalculator.addition(mInput01, mInput02);  // first number + second number = result.
        // Assert
        assertEquals(result, mExpect);
    }

}

تعقيب

قمنا بجعل هذه الكلاس تعمل كمشغل Parameterized وذلك من خلال استخدام النوتيشن RunWith اعلاها. ثم أنشأنا حقل لعنصر الموديل لدينا Calculator. وايضاً أنشأنا ثلاثة حقول وهي mInput01 و mInput02 و mExpect حتى نخزن فيهم قيمة المدخلات والمخرجات لاحقاً. و جهزنا مانريد في الدالة setUp ذي النوتيشن Before قبل بدء دالة الاختبار.
ثم قمنا بانشاء Constructor للكلاس ياخد ثلاثة عناصر ويضع قيمتهم في الحقول الثلاثة السابقه. وبعد ذلك قمنا بكتابة دالة data تقوم بإرجاع لسته وبها جميع المدخلات والمخرجات التي نريد التحقق منهم ووضع النوتيشن Parameterized.Parameters فوقها, حتى تصبح هي المزود للـ Constructor والذي بدوره سوف يضع هذه القيم في الحقول الثلاثة في كل تشغيل له لكل احتمال.
واخيراً قمنا بكتابة دالة الاختبار additions_test والتي سوف تختبر الدالة addition من الموديل Calculator بجميع المدخلات و المخرجات بلستة الدالة data.

 

النتيجه

parresult.png.45ec5fedfbd92d76dbf228625ef5e686.png

تعقيب

جميع الاختبارات التالية قد نجحت, اذن الدالة addtion لايوجد بها خلل.

2 + 4 = 6
4 + 4 = 8
-11 + 4 = -7
66 + 395 = 461
1000 + 4 = 996
99 + 1 = 100

وهكذا قمنا باختبار الكثير من المدخلات والمخرجات لدالة بكتابة دالة اختبار واحدة فقط.

 

 

استخدام Theories في الـ JUnit
نستخدم الـ Theories (النظريات) اذا اردنا حصر اي دالة في افتراضات (Assumptions) نحن نحددها, وشرط نجاح الاختبار هو ان تكون النتيجة دائماً صحيحة (اي true). فهي مشابهه جداً للـ Parameterized, ولكن الفرق هنا ان هذه تختلف في المدخلات فقط, وتتشابه في النتيجه وهي true في كل الاحوال.

اقتباس

الهدف من النظرية هو استخدام الافتراضيات مع التأكيدات بشكل مناسب, حتى يتم تغطية الكثير من الاحتمالات باستخدام (DataPoints) لجعل كتابة الاختبارات اكثر مرونه, وجعلها اقرب الى النظريات الحقيقة

 

ماهي النظرية التي سوف نكتبها

نظرية تقوم باخد مدخلين ثم تقوم بجمع فقط المدخلات الذي يكون الناتج لهم زوجي.

 

خطوات كتابة كلاس Theories لإنشاء نظرية اختبار

  1. نقوم بعمل كلاس مشغل للـ Theories وذلك من خلال النوتيشن RunWith كما في حال المشغلات بالمقالة السابقة.
  2. نقوم بكتابة الـ Fields التي سوف نحتاجها في هذه الكلاس مثل عنصر من الموديل Calculator.java.
  3. نكتب دالة setUp ذي النوتيشن Before, لتجهيز عناصرنا المراد اجراء الاختبارات عليهم.
  4. ننشئ دالة data ونجعلها static تقوم بإرجاع المدخلات التي نريد اختبار الدالة بها, ونضع النوتيشن DataPoints فوقها.
  5. الان نقوم بكتابة النظرية (بالنوتيشن Theory وليس Test), والتي سوف تختبر ناتج فرضيات وتأكيدات نحن نريدها باستخدام الدالة addition من الموديل Calculator.

الكود لكلاس مشغل الـ Theories


package com.company;

import org.junit.Before;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.*;

// This class become Theories.class Runner.
@RunWith(Theories.class)
public class TheoriesTest {

    // Fields
    private Calculator mCalculator;


    @Before
    public void setUp(){
        // Arrange
        mCalculator = new Calculator("Purple");
    }

    // Our Data Points (the inputs)
    @DataPoints
    public static int[] data() {
        return new int[]{
                2, 6, 8, 7, 3, 0
        };
    }

    // Our @Theory Method, not a @Test
    @Theory
    public void addition_even_result_test(int value1, int value2){
        // Assumptions -> assumeTrue(), assumeNotNull(), assumeNoException(), assumeThat(), etc...
        assumeTrue(value1 % 2 == 0);
        assumeTrue(value2 % 2 == 0);
        assumeTrue( value1 != 0);
        assumeTrue( value2 != 0);

        // Action
        int sumResult = mCalculator.addition(value1, value2);

        // Assert
        assertTrue( sumResult % 2 == 0);

        // Just for demonstration
        System.out.println(value1 + " + " + value2 + " = " + sumResult +" is even");
    }

}

تعقيب

قمنا بجعل هذه الكلاس تعمل كمشغل Theories وذلك من خلال استخدام النوتيشن RunWith اعلاها. ثم أنشأنا حقل لعنصر الموديل لدينا Calculator. وجهزنا مانريد في الدالة setUp ذي النوتيشن Before قبل بدء دالة النظرية.
ثم قمنا بكتابة دالة data تقوم بإرجاع لسته وبها جميع المدخلات التي نريد التحقق منهم ووضع النوتيشن DataPoints فوقها, حتى تصبح هي المزود لدالة النظرية لدينا لاحقاً.
واخيراً قمنا بكتابة دالة النظرية addition_even_result_test والتي سوف تختبر الدالة addition من الموديل Calculator لجميع من المدخلات بلستة الدالة data.

  • النظرية تختلف عن الدالة في انها تستخدم النوتيشن Theory. ايضاً تختلف عن الدالة في انها تأخد مدخلات parameters.
  • النظرية تأخد مدخلاتها (parameters) من الدالة data المعلمه بالنوتيشن DataPoints. تستطيع ايضاً استعمال Fields ولكن تعلمها بالنوتيشن DataPoint.
  • النظرية في بدايتها نقوم بكتابة الافتراضيات وهي باستخدام دوال الـ assume التي تأتي مع الـ JUnit. ثم نقوم بكتابة الفعل ومن ثم التأكيد.

ناتج تشغيل كلاس النظرية (لاحظ كيف ان النظرية تقوم بتجربة جميع الاحتمالات للمدخلات بمزج وخلط وعكس الـ DataPoints)


2 + 2 = 4 is even
2 + 6 = 8 is even
2 + 8 = 10 is even
6 + 2 = 8 is even
6 + 6 = 12 is even
6 + 8 = 14 is even
8 + 2 = 10 is even
8 + 6 = 14 is even
8 + 8 = 16 is even

تعقيب

لدينا البيانات التالية : 2, 6, 8, 7, 3, 0 التي سنستخدمهم داخل النظرية. وقمنا بكتابة فرضيات (باستخدام الـ assume) وهي: لنفرض ان المدخل الاول و المدخل الثاني لايساويان صفر, وايضاً لنفرض ان ناتج باقي القسمة للمدخل الاول والثاني يساوي صفر (لماذا؟ حتى نتأكد ان العدد هو رقم زوجي) هكذا:


        assumeTrue(value1 % 2 == 0);
        assumeTrue(value2 % 2 == 0);
        assumeTrue( value1 != 0);
        assumeTrue( value2 != 0);

والان كل ماينطبق عليه هذه الشروط من DataPoints سوف تقوم النظرية باستخدامه في الفعل (addition), وعكس ذلك سوف تقوم بتجاهله. ونلاحظ انها تجاهلة الصفر والاعداد الفردية (كما هو ظاهر في ناتج تشغيلها بالسابق).


        // Action
        int sumResult = mCalculator.addition(value1, value2);

ثم ستقوم النظرية بالتأكد هل ناتج الجمع هو زوجي.


        // Assert
        assertTrue( sumResult % 2 == 0);

وهكذا توصلنا الى الاعداد الذي يكون ناتج الجمع لهم زوجي.
 

 

والحمد لله تم الانتهاء من هذه السلسلة التي تعتبر مدخل مناسب لمن يريد الدخول في عالم الاختبارات في تطوير تطبيقات الاندرويد, والذي سوف اتطرق له ان شاء الله في المستقبل في مقالات اخرى.

كلمات دليلية: java junit
2
إعجاب
2337
مشاهدات
0
مشاركة
0
متابع
متميز
محتوى رهيب

التعليقات (0)

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !