تأملات في اختبار المؤسسة الفعال

مرحبا يا هابر!

لقد عدنا مؤخرًا إلى دراسة شاملة لموضوع الاختبار ، وفي الخطط المتوقعة لدينا كتاب ممتاز عن اختبار الوحدة. في الوقت نفسه ، نعتقد أن السياق مهم في هذا الموضوع وليس في أي مكان آخر ، لذلك نقدم اليوم ترجمة لمنشورين (مجتمعتين في واحد) تم نشرهما على مدونة أحد المتخصصين البارزين في Java EE سيباستيان داشنر - أي 1/6 و 2/6 من السلسلة " أفكار بشأن اختبار كفاءة المؤسسة. "

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

المقدمة


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

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

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

المبادئ والقيود


بغض النظر عن الحلول التي سيتم تحديدها ، دعنا نحدد المبادئ والقيود التالية لمجموعة الاختبار لدينا:

  • , . , . , , .
  • , , . , , , . , , .
  • - . , , .
  • , , . : « HTTP- gRPC, JSON - enterprise-, ..?”.
  • , , -. API, DSL .
  • « », , , , , , “dev” debug () , dev Quarkus', Telepresence, watch-and-deploy (« ») .
  • . , , , , . .
  • , , -, , , . , , , .


يتحقق اختبار الوحدة من سلوك وحدة مفردة ، عادةً ما تكون فئة ، بينما يتم تجاهل أو محاكاة جميع العوامل الخارجية التي لا تتعلق ببنية الوحدة. يجب أن تتحقق اختبارات الوحدة من منطق الأعمال للوحدات الفردية دون التحقق من تكاملها أو تكوينها.

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

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

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

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

اختبار حالات التطبيق


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

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

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

لتخيل كل هذا بشكل أفضل ، افترض أننا نختبر فصلًا يصف ترتيب القهوة. يشتمل هذا الفصل على فصلين آخرين: CoffeeShopو OrderProcessor.



فئات الزوجي اختبار، CoffeeShopTestDoubleو OrderProcessorTestDoubleأنها *TDتقع في منطقة الاختبار للمشروع، حيث ترث مكونات CoffeeShopو OrderProcessorيقع في المنطقة الرئيسية من البرنامج. يمكن لنظراء الاختبار تعيين المحاكاة اللازمة ومنطق الاتصال وربما توسيع الواجهة العامة للفصل باستخدام طرق المحاكاة المطلوبة في هذا التطبيق أو عن طريق طرق التحقق.

يوضح ما يلي فئة الاختبار المزدوجة للمكون CoffeeShop:

public class CoffeeShopTestDouble extends CoffeeShop {

    public CoffeeShopTestDouble(OrderProcessorTestDouble orderProcessorTestDouble) {
        entityManager = mock(EntityManager.class);
        orderProcessor = orderProcessorTestDouble;
    }

    public void verifyCreateOrder(Order order) {
        verify(entityManager).merge(order);
    }

    public void verifyProcessUnfinishedOrders() {
        verify(entityManager).createNamedQuery(Order.FIND_UNFINISHED, Order.class);
    }

    public void answerForUnfinishedOrders(List<Order> orders) {
        //     
    }
}

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

فئات الزوجي الاختبار هي مكونات قابلة لإعادة الاستخدام ، يتم كتابة كل منها مرة واحدة لكل نطاق لكل مشروع ، ثم يتم استخدامه في العديد من الحالات العملية:

class CoffeeShopTest {

    private CoffeeShopTestDouble coffeeShop;
    private OrderProcessorTestDouble orderProcessor;

    @BeforeEach
    void setUp() {
        orderProcessor = new OrderProcessorTestDouble();
        coffeeShop = new CoffeeShopTestDouble(orderProcessor);
    }

    @Test
    void testCreateOrder() {
        Order order = new Order();
        coffeeShop.createOrder(order);
        coffeeShop.verifyCreateOrder(order);
    }

    @Test
    void testProcessUnfinishedOrders() {
        List<Order> orders = Arrays.asList(...);
        coffeeShop.answerForUnfinishedOrders(orders);

        coffeeShop.processUnfinishedOrders();

        coffeeShop.verifyProcessUnfinishedOrders();
        orderProcessor.verifyProcessOrders(orders);
    }

}

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

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

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

All Articles