كيفية بناء هرم في تطبيقات التنمية الجذع أو يحركها اختبار على التمهيد الربيع

غالبًا ما يُستشهد بإطار عمل الربيع كمثال على إطار عمل Cloud Native ، المصمم للعمل في السحابة ، وتطوير تطبيقات Twelve-Factor ، والخدمات المصغرة ، وواحد من أكثر المنتجات استقرارًا ، ولكن في نفس الوقت. ولكن في هذه المقالة ، أود أن أتطرق إلى جانب واحد أكثر قوة من الربيع: إنه دعمه التنموي من خلال الاختبار (قدرة TDD؟). على الرغم من اتصال TDD ، لاحظت في كثير من الأحيان أن مشاريع الربيع إما تتجاهل بعض أفضل الممارسات للاختبار ، أو تخترع دراجاتها الخاصة ، أو لا تكتب اختبارات على الإطلاق لأنها "بطيئة" أو "غير موثوق بها". وسأخبرك بالضبط بكيفية كتابة اختبارات سريعة وموثوقة للتطبيقات في Spring Framework وإجراء التطوير من خلال الاختبار. لذلك إذا كنت تستخدم Spring (أو تريد البدء) ، contextLoads ما هي الاختبارات بشكل عام (أو تريد أن تفهم) ، أو تعتقد أن contextLoads هي المستوى الضروري والكافي لاختبار التكامل - سيكون الأمر مثيرًا للاهتمام!


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


  • اختبار التكامل - يمكنك بسهولة تشغيل التطبيق ومكونات القفل وإعادة تعريف المعلمات ، إلخ.
  • اختبار دمج التركيز - الوصول فقط إلى البيانات ، فقط الويب ، إلخ.
  • دعم خارج الصندوق - قواعد البيانات في الذاكرة ، وقوائم انتظار الرسائل ، والمصادقة والتفويض في الاختبارات
  • الاختبار من خلال العقود (Spring Cloud Contract)
  • دعم اختبار واجهة المستخدم على الويب باستخدام HtmlUnit
  • مرونة تكوين التطبيق - التشكيلات الجانبية وتكوينات الاختبار والمكونات وما إلى ذلك.
  • وغير ذلك الكثير

بادئ ذي بدء ، مقدمة صغيرة ولكنها ضرورية حول TDD والاختبار بشكل عام.


تطوير مدفوع باختبار


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


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


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


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


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


  • "TDD هي تغطية 100٪ تقريبًا للشفرة ، لكنها لا تقدم ضمانات" - التطوير من خلال الاختبار لا علاقة له بتغطية 100٪ على الإطلاق. في العديد من الفرق التي عملت فيها ، لم يتم قياس هذا المقياس حتى ، وتم تصنيفه كمقياس الغرور. ونعم ، تغطية الاختبار 100٪ لا تعني شيئًا.
  • "TDD يعمل فقط للوظائف البسيطة ، والتطبيق الحقيقي مع قاعدة البيانات والحالة الصعبة لا يمكن أن تصنع معه" هو عذر شائع جدًا ، وعادة ما يتم استكماله بـ "لدينا تطبيق معقد لا نكتب فيه اختبارات على الإطلاق ، لا يمكنك القيام بذلك على الإطلاق". لقد رأيت نهج TDD فعال على تطبيقات مختلفة تمامًا - الويب (مع وبدون SPA) ، والهاتف المحمول ، و API ، والخدمات الصغيرة ، والموحد ، والأنظمة المصرفية المعقدة ، والمنصات السحابية ، والأطر ، ومنصات البيع بالتجزئة المكتوبة بلغات وتقنيات مختلفة. لذا فإن الأسطورة الشعبية "نحن فريدون ، كل شيء مختلف" هي في الغالب عذر لعدم استثمار الجهد والمال في الاختبار ، ولكنها ليست سببًا حقيقيًا (على الرغم من أنه قد تكون هناك أيضًا أسباب حقيقية).
  • "ستظل هناك أخطاء مع TDD" - بالطبع ، كما هو الحال في أي برنامج آخر. TDD لا تتعلق بالأخطاء أو غيابها على الإطلاق ، إنها أداة تطوير. مثل التصحيح. مثل IDE. مثل الوثائق. لا تضمن أي من هذه الأدوات عدم وجود أخطاء ، فهي تساعد فقط على التعامل مع التعقيد المتزايد للنظام.

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

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


نموذج خطة لسلسلة من المقالات ، كما أراها في الوقت الحالي ، هو:


  1. هيكل عظمي للمشي - إطار عمل حيث يمكنك تشغيل دورة Red-Green-Refactor
  2. اختبار واجهة المستخدم والتصميم المدفوع بالسلوك
  3. اختبار الوصول إلى البيانات (بيانات الربيع)
  4. اختبار المصادقة والمصادقة (Spring Security)
  5. Jet Stack (مفاعل WebFlux + Project)
  6. قابلية التشغيل البيني للخدمات والعقود (الدقيقة) (Spring Cloud)
  7. اختبار خدمة وضع الرسائل في قائمة انتظار (Spring Cloud)

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


قصة المستخدم عبارة عن وصف لميزة لتطبيق لغة طبيعية يتم كتابتها عادةً نيابة عن مستخدم النظام.

قصة المستخدم 1: يرى المستخدم صفحة الترحيب


كما أليس ، مستخدم جديد
أريد أن أرى صفحة ترحيب عند زيارة موقع Cake Factory الإلكتروني
حتى أعلم متى توشك كيك فاكتوري على الإطلاق

معايير القبول:
السيناريو: مستخدم يزور الموقع قبل موعد الإطلاق
بالنظر إلى أنني مستخدم جديد
عندما أقوم بزيارة موقع Cake Factory على الويب
ثم تظهر لي رسالة "شكرًا على اهتمامك"
وأرى رسالة "الموقع الإلكتروني قريبًا ..."

سوف يتطلب الأمر معرفة: ما هو التطور القائم على السلوك والخيار ، أساسيات اختبار التمهيد الربيعي .


أول قصة مستخدم أساسية تمامًا ، لكن الهدف ليس معقدًا بعد ، ولكن في إنشاء هيكل عظمي للمشي - وهو تطبيق بسيط لبدء دورة TDD .


بعد إنشاء مشروع جديد في Spring Initializr مع build.gradle الويب والشارب ، في البداية ، سأحتاج إلى بعض التغييرات build.gradle :


  • أضف HtmlUnit testImplementation('net.sourceforge.htmlunit:htmlunit') . لا تحتاج إلى تحديد الإصدار ، سيحدد المكوّن الإضافي لإدارة تبعية Spring Boot لـ Gradle تلقائيًا الإصدار الضروري والمتوافق
  • ترحيل مشروع من JUnit 4 إلى JUnit 5 (لأن 2018 في الفناء)
  • أضف تبعيات إلى Cucumber - مكتبة سأستخدمها لكتابة مواصفات BDD
  • إزالة تم إنشاؤها بشكل افتراضي CakeFactoryApplicationTests مع contextLoads لا مفر منه

بشكل عام ، هذا هو "الهيكل العظمي" الأساسي للتطبيق ، يمكنك بالفعل كتابة الاختبار الأول.


لتسهيل التنقل في التعليمات البرمجية ، سأتحدث باختصار عن التقنيات المستخدمة.


خيار


الخيار هو إطار عمل يعتمد على السلوك ويساعد على إنشاء "مواصفات قابلة للتنفيذ" ، أي إجراء الاختبارات (المواصفات) المكتوبة بلغة طبيعية. يوزع البرنامج المساعد الخيار شفرة المصدر في جافا (والعديد من اللغات الأخرى) ويستخدم تعريفات الخطوة لتشغيل التعليمات البرمجية الحقيقية. تعريفات الخطوة هي أساليب الصف التي تم @Given عليها بواسطة @Given و @When و @Then والتعليقات التوضيحية الأخرى.


هتملونيت


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


الاختبار الأول


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


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

لذا ، أول ميزة لي.


 Feature: Welcome page Scenario: a user visiting the web-site visit before the launch date Given a new user, Alice When she visits Cake Factory web-site Then she sees a message 'Thank you for your interest' And she sees a message 'The web-site is coming in December!' 

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


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


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

أحمر


لذلك ، بالنسبة إلى الميزة الأولى ، أنشأت أوصاف الخطوات التالية:


 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class WelcomePage { private WebClient webClient; private HtmlPage page; @LocalServerPort private int port; private String baseUrl; @Before public void setUp() { webClient = new WebClient(); baseUrl = "http://localhost:" + port; } @Given("a new user, Alice") public void aNewUser() { // nothing here, every user is new by default } @When("she visits Cake Factory web-site") public void sheVisitsCakeFactoryWebSite() throws IOException { page = webClient.getPage(baseUrl); } @Then("she sees a message {string}") public void sheSeesAMessageThanksForYourInterest(String expectedMessage) { assertThat(page.getBody().asText()).contains(expectedMessage); } } 

بضع نقاط يجب الانتباه إليها:


  • يتم تشغيل الميزات من خلال ملف آخر ، Features.java باستخدام التعليقات التوضيحية RunWith من JUnit 4 ، لا يدعم الخيار الإصدار 5 ، للأسف
  • @SpringBootTest إضافة التعليق التوضيحي @SpringBootTest إلى وصف الخطوات ، حيث يلتقط cucumber-spring من هناك @SpringBootTest سياق الاختبار (أي يبدأ التطبيق)
  • يبدأ تطبيق Spring للاختبار مع webEnvironment = RANDOM_PORT ويتم webEnvironment = RANDOM_PORT هذا المنفذ العشوائي إلى الاختبار باستخدام @LocalServerPort ، @LocalServerPort Spring هذا التعليق التوضيحي @LocalServerPort قيمة الحقل إلى منفذ الخادم

والاختبار ، كما هو متوقع ، يتعطل مع الخطأ 404 for http://localhost:51517 .


تعتبر الأخطاء التي يتعطل بها الاختبار مهمة للغاية ، خاصة عندما يتعلق الأمر باختبارات الوحدة أو التكامل ، وهذه الأخطاء جزء من واجهة برمجة التطبيقات. إذا تعطل الاختبار باستخدام NullPointerException فهذا ليس جيدًا جدًا ، ولكن BaseUrl configuration property is not set - أفضل بكثير.

أخضر


لجعل الاختبار أخضرًا ، أضفت وحدة تحكم أساسية وعرضًا باستخدام الحد الأدنى من HTML:


 @Controller public class IndexController { @GetMapping public String index() { return "index"; } } 

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Cake Factory</title> </head> <body> <h1>Thank you for your interest</h1> <h2>The web-site is coming in December!</h2> </body> </html> 

الاختبار أخضر ، يعمل التطبيق ، على الرغم من أنه مصنوع في تقليد التصميم الهندسي الشديد.


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

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


ريفاكتور


في التكرار الأول ، ليس هناك إعادة بيع ديون معينة ، ولكن على الرغم من أنني قضيت العشر دقائق الأخيرة في اختيار قالب لبولما ، والذي يمكن اعتباره إعادة بيع ديون!


في الختام


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


المراجع



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

Source: https://habr.com/ru/post/ar431306/


All Articles