استخدام الـ Rules في الـ JUnit لإضافة سلوكيات لدوال الاختبارات

Mohammad Laifمنذ 6 سنوات

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

في هذه المقالة سنتعلم كيفية استخدام القواعد (Rules) في الـ JUnit والتي تسمح لنا بأضافة سلوك (behavior) جديد او التغيير في بعض السلوكيات على دوال الاختبارات.
 

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

هذه هي المقالات السابقة والتي تعتبر ضروريه لفهم هذا المقال فاذا لم تكن لديك خلفيه مع الـ 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://github.com/mzdhr/KidCalculator-JUnit

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

 

ماذا سوف تقرئ في هذه المقالة
ماهي القواعد Rule و كيفية استخدامها؟ ما الفرق بين النوتيشن ClassRule و Rule وكيفية استخدامهم ومتى؟ امثله لإستخدام بعض من القواعد كقاعدة الـ Timeout و الـ TestName و الـ ErrorCollector. واخيراً كيفية انشاء قاعدة خاصة بنا؟

 

ماهي القواعد (Rule) للـ JUnit

هي عبارة عن عناصر منئشه من كلاس قاعدي (اي كلاس يعمل implements للإنترفيس TestRule يعتبر كلاس قاعدي). فأنت تقوم بأنشائها كـ Field في كلاس الاختبارات.

 

كيفية استخدام هذه القواعد Rules في الـ JUnit

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

 

قائمة بجميع القواعد الموجوده في الـ JUnit

هذه كلاسات قواعد معده مسبقاً, وجاهزه لإنشاء عناصر منها واستخدامها مع النوتيشن Rule و ClassRule في كلاس اختباراتك.

  • TemporaryFolder لإنشاء ملفات ومجلدات وهميه, لإستخدامهم عند الحاجة اثناء كتابة الاختبارات.
  • ExternalResource مثل سابقتها, ولكن هنا تنشئ عناصر وهميه مثل التعامل مع: socket و server و database connection الخ...
  • ErrorCollector تسمح لدوال الاختبار بالاستمرار للنهاية, حتى مع حدوث خطئ لاتتوقف, وتقوم بجمع كل الاخطاء ومن ثم عرضهم.
  • Verifier اذا اردت ان تتحقق من شئ قبل بداية الاختبارات, مثلاً هل شبكة الانترنت متصلة ام لا.
  • TestWatchman/TestWatcher تسمح لك هذه القاعدة بمراقبة دوال الاختبارات و طباعة Log في الكونسول للمراقبة, مثلاً هل بدء الاختبار؟ هل انتهى؟ هل نجح ام فشل؟. 
  • TestName اذا اردت الحصول على اسم دالة الاختبار فهذه القاعدة هي المسؤله عن ذلك, مثلاً تستطيع من خلالها طباعة اسم دالة الاختبار من داخلها.
  • Timeout لوضع حد زمني لدوال الاختبارات, على ان تكتمل كل دالة قبل الوصول لذلك الزمن وإلا تفشل.
  • ExpectedException تسمح لك بالحصول على تحكم اكثر في رسالة ونوع الـ exception المرمي في دالة الاختبار.
  • RuleChain لربط القواعد مع بعضها البعض.

 

قائمة بـ نوتيشنات الخاصه للقواعد (annotation) الموجوده في الـ JUnit

  • Rule تستخدم لتطبيق القاعدة على جميع دوال الاختبارات.
  • ClassRule تستخدم لتطبيق القاعدة على كلاس الاختبارات فقط وليس على دوالها.

 

استخدام النوتيشن ClassRule مع القاعدة Timeout

أستخدام النوتيشن ClassRule والذي من خلاله نستطيع تطبيق  اي قاعده يأتي فوقها على الكلاس نفسها وليس على دوالها.

اما القاعدة Timeout فهي تسمح لتحديد وقت (بأجزاء الثانية)  حتى ينتهي الاختبار في مدة هذا الوقت والا يعتبر الاختبار فاشل.

المثال


    // Rule for Class
    @ClassRule
    public static TestRule timeout = Timeout.millis(5000);

تعقيب

بانشاء هذا الـ Field من كلاس القاعدة Timeout وتحديد النوتيشن له بـ ClassRule (لاحظ انه static لانه يعود للكلاس نفسها) فهذا يعني: أن كلاس الاختبارات يجب ان تنهي جميع اختباراتها في اقل من ٥ ثواني, والا تعتبر الاختبارات بها فاشله.

 

اذن متى نستخدم النوتيشن الـ ClassRule؟

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

اقتباس

اذن من هذا التصرف نرى ان النوتيشن ClassRule تشابه عملها للنوتيشن BeforeClass و AfterClass كما بالمقال الاول, ولكنها هنا تختص بالقواعد.

 

استخدام النوتيشن Rule مع القاعدة Timeout

هنا سنستخدم نفس القاعدة Timeout لتحديد فتره زمنيه, ولكن ليس على الكلاس! بل على جميع دوال الاختبارات باستخدام النوتيشن Rule كما بالمثال التالي.

المثال


    // Rules for @Test Methods
    @Rule
    public final TestRule timeoutForMethods = Timeout.millis(1005);

تعقيب

بانشاء هذا الـ Field من كلاس القاعدة Timeout وتحديد النوتيشن له بـ Rule فهذا يعني: أن اي دالة اختبار تأتي بالنوتيشن Test عليها ان تنهي عملها قبل تخطي هذا الوقت وهو تقريباً ثانيه, والا سوف تفشل.

اقتباس

اليس هذا مشابهه للخاصة timeout التي تأتي مع النوتيشن Test كما بالمقال الاول, ولكن الفرق هنا ان هذه نكتبها فقط كـ Field وتطبق على جميع الدوال, اي لاحاجة لتكرار كتابة timeout لكل نوتيشن Test.

 

اذن متى نستخدم النوتيشن الـ Rule؟

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

اقتباس

اذن من هذا التصرف نرى ان النوتيشن Rule تشابه عملها للنوتيشن Before و After كما بالمقال الاول, ولكنها هنا تختص بالقواعد.

 

استخدام النوتيشن Rule مع القاعدة TestName

مثال اخر لإستخدام النوتيشن Rule مع قاعدة اخرى وهي TestName, وكما جاء سابقاً للـ "TestName: اذا اردت الحصول على اسم دالة الاختبار فهذه القاعدة هي المسؤله عن ذلك, مثلاً تستطيع من خلالها طباعة اسم دالة الاختبار من داخلها."

المثال لأنشائها


    @Rule
    public final TestName testName = new TestName();

تعقيب

عباره عن Field بكلاس الاختبارات.

 

المثال لإستخدامها


    @Test
    public void method_name_test(){
        System.out.println("This message printed from method -> " + testName.getMethodName());
    }

الناتج

This message printed from method -> method_name_test

تعقيب

قمنا باستخدام الفيلد testName بداخل دالة الاختبار "method_name_test", لطباعة اسمها.

 

استخدام النوتيشن Rule مع القاعدة ErrorCollector

مثال اخر لإستخدام النوتيشن Rule مع قاعدة اخرى وهي ErrorCollector , وكما جاء سابقاً للـ "ErrorCollector: تسمح لدوال الاختبار بالاستمرار للنهاية, حتى مع حدوث خطئ لاتتوقف, وتقوم بجمع كل الاخطاء ومن ثم عرضهم."

المثال لإنشائها


    @Rule
    public final ErrorCollector errorCollector = new ErrorCollector();

تعقيب

عباره عن Field بكلاس الاختبارات.

 

المثال لإستخدامها


    @Test
    public void error_collector_test(){
        // Action
        int result01 = calculator.addition(2,2);
        int result02 = calculator.addition(3,3);
        int result03 = calculator.addition(4,4);
        // Assertion
        errorCollector.checkThat(result01, is(3));  // wrong
        errorCollector.checkThat(result02, is(3));  // wrong
        errorCollector.checkThat(result03, is(3));  // wrong
    }

النتيجه

errorcollectorjunitt.thumb.png.a233ae4e3a7a6f6a5306615ebc77c3dc.png

تعقيب

قمنا باستخدام الفيلد errorCollector مع الدالة checkThat واعطيناه ثلاثة احتمالات خاطئه عمداً, لنرى هل يقوم فقط بالتحقق من الاحتمال الاول ثم يفشل ويوقف الاختبار! كما سنرى كيف يحصل مع الداله assertThat لاحقاً! ام يكمل ويأتينا بالاخطاء كلها (نعم اتى بالاخطاء كلها).

 

الاستخدام بدون القاعدة الـ Error Collector


    @Test
    public void no_error_collector_test(){
        // Action
        int result01 = calculator.addition(2,2);
        int result02 = calculator.addition(3,3);
        int result03 = calculator.addition(4,4);
        // Assertion
        assertThat(result01, is(3));  // wrong
        assertThat(result02, is(3));  // wrong
        assertThat(result03, is(3));  // wrong
    }

النتيجه

noerorrcollector.png.9e85530f89900e21fc1ab3fdfe66fe1d.png

تعقيب

بدون استخدام القاعدة ErrorCollector فنحن سنحصل فقط على اول خطئ ثم يتوقف عمل دالة الاختبار.

 

انشاء قاعدة خاصة بنا واستخدام النوتيشن Rule معها

ماذا لو ان القواعد المدمجه مع الـ JUnit لم تلبي رغبة كتابة اختباراتك لمشروعك! وتحتاج المزيد او غيرها من القواعد؟ اذن في هذه الحاله تستطيع ان تنشئ قاعدتك الخاصة كما سيأتي.

 

ماهي وظيفة القاعدة التي سوف ننشئها؟

سننشئ قاعدة تقوم بإضافة سلوك اضافي لكل دالة اختبار لدينا. وهذا السلوك هو: ان كل دالة اختبار تقوم بطباعة ثلاثة اشياء: اسمها و اسم كلاسها و كم استغرقت من وقت.

 

خطوات انشاء قاعدة خاصة في الـ JUnit

  1. انشاء كلاس اختبارات جديدة في مسار مجلد الاختبارات.
  2. عمل implements للـ interface التاليه TestRule.
  3. عمل Override للدالة apply.
  4. انشاء عنصر جديد من كلاس الـ Statement وجعل الدالة apply تقوم بإرجاعه.
  5. وهذا العنصر يحتوي على الدالة evaluate والتي بداخلها سوف نقوم بكتابة الكود الذي سيضيف هذا السلوك الجديد لدوال الاختبارات.

 

الكود (كلاس القاعدة الخاصة بنا)


package com.company;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;


public class RuleWatchEye implements TestRule {
    
    @Override
    public Statement apply(Statement base, Description description) {
        
        return new Statement() {
            
            @Override
            public void evaluate() throws Throwable {
                // ---------- Write Behavior ----------
                long start = System.currentTimeMillis();
                try {
                    // This code will run when each of @Test Methods runs.
                    base.evaluate();    // Trigger for our @Test Methods code's.
                    System.out.println();
                    System.out.println("Method   ---> " + description.getMethodName());
                    System.out.println("In Class ---> " + description.getTestClass().getName());
                } finally {
                    // This code will run when each of @Test Methods is finish running.
                    System.out.println("Takes    ---> " + (System.currentTimeMillis() - start) + " Millisecond");
                }
                // ---------- End of Behavior ----------
            }
            
        };
        
    }
    

}

تعقيب

العمل كله يحدث داخل الدالة "evaluate" حيث اننا قمنا بأنشاء العنصر start وجعلناه يخزن وقت البدء. ثم بداخل الـ Try قمنا بكتابة ماذا نريد ان يتم عند تشغيل اي دالة اختبارات. وفي الـ finally قمنا بكتابة ماذا نريد ان يتم عند الانتهاء من تشغيل اي دالة اختبارات. ولاتنسى قرائة التعليقات الموجودة بالكود للإيضاح اكثر, وايضاً قرائة خطوات الانشاء السابقه حتى يسهل الفهم.

 

طريقة الاستخدام


    // Custom Rule
    @Rule
    public final RuleWatchEye ruleWatchEye = new RuleWatchEye();

تعقيب

فقط نقوم بعمل عنصر كـ Field منها في كلاس اختباراتنا. ونضع فوقه النوتيشن Rule, وعند تشغيل اي دالة اختبار فسوف يتم تفعيل قاعدتنا عليها.

 

الناتج

Method   ---> time_out_annotation_test
In Class ---> com.company.RuleTest
Takes    ---> 803 Millisecond

تعقيب

تم تشغيل الدالة time_out_annotation_test في كلاس الاختبارات RuleTest.java بشكل عادي, وتمت طباعة هذه الاسطر الثالثه السابقه, اذن قاعدتنا تعمل بشكل ممتاز. واذا اردت ان تقوم بطباعة جميع المعلومات للدوال الاخرى فقط قم بتشغيل الكلاس بأكملها. ونستطيع ايضاً استخدام هذه القاعد (وذلك فقط بانشاء عنصر منها كـ Field) في كلاسات اختباراتنا الاخرى.

 

الكود الكامل لكلاس الاختبارات للقواعد في هذه المقالة


package com.company;

import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.junit.rules.TestName;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

public class RuleTest {
    // Fields
    private Calculator calculator;

    // Rule for Class
    @ClassRule
    public static TestRule timeout = Timeout.millis(5000);

    // Rules for @Test Methods
    @Rule
    public final TestRule timeoutForMethods = Timeout.millis(1005);
    @Rule
    public final TestName testName = new TestName();
    @Rule
    public final ErrorCollector errorCollector = new ErrorCollector();

    // Custom Rule
    @Rule
    public final RuleWatchEye ruleWatchEye = new RuleWatchEye();

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

    @Test(timeout = 1000)
    public void time_out_annotation_test() throws InterruptedException {
        Thread.sleep(800);
    }

    @Test
    public void time_out_global_annotation_test() throws InterruptedException {
        Thread.sleep(800);
    }

    @Test
    public void error_collector_test(){
        // Action
        int result01 = calculator.addition(2,2);
        int result02 = calculator.addition(3,3);
        int result03 = calculator.addition(4,4);
        // Assertion
        errorCollector.checkThat(result01, is(3));  // wrong
        errorCollector.checkThat(result02, is(3));  // wrong
        errorCollector.checkThat(result03, is(3));  // wrong
    }

    @Test
    public void no_error_collector_test(){
        // Action
        int result01 = calculator.addition(2,2);
        int result02 = calculator.addition(3,3);
        int result03 = calculator.addition(4,4);
        // Assertion
        assertThat(result01, is(3));  // wrong
        assertThat(result02, is(3));  // wrong
        assertThat(result03, is(3));  // wrong
    }


    @Test
    public void method_name_test(){
        System.out.println("This message printed from method -> " + testName.getMethodName());
    }

}

 

 

في النهاية تعلمنا كيفية استخدام النوتيشن Rule و ClassRule والفرق بينهم. وتطرقنا الى بعض من الامثله للقواعد المدمجة البسيطه (لتسهيل الفهم) مع الـ JUnit كـ ErrorCollector و TestName و Timeout  (للإطلاع على المزيد من الامثله لهذه القواعد), وكيفية استخدامهم مع هذه النوتيشنات. وايضاً تعلمنا كيفية انشاء قاعدة خاصة بنا وكيفية استخدامها وتطبيقها في كلاسات اختباراتنا.

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

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

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

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