أكثر 10 أخطاء شائعة في إطار الربيع

نشرت: 2022-03-11

يمكن القول إن Spring هو أحد أكثر أطر عمل Java شيوعًا ، وهو أيضًا وحش عظيم يجب ترويضه. في حين أن المفاهيم الأساسية سهلة الفهم إلى حد ما ، فإن التحول إلى مطور Spring قوي يتطلب بعض الوقت والجهد.

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

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

الخطأ الشائع الأول: الوصول إلى مستوى منخفض للغاية

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

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

لذا احتضن الأفكار التجريدية - في المرة القادمة التي تواجه فيها مشكلة معينة ، قم بإجراء بحث سريع أولاً وحدد ما إذا كانت المكتبة تحل هذه المشكلة مدمجة بالفعل في الربيع ؛ في الوقت الحاضر ، من المحتمل أن تجد حلاً مناسبًا موجودًا. كمثال لمكتبة مفيدة ، سأستخدم التعليقات التوضيحية لمشروع لومبوك في أمثلة لبقية هذه المقالة. يتم استخدام Lombok كمولد رمز معياري ونأمل ألا يواجه المطور الكسول بداخلك مشكلة في التعرف على المكتبة. كمثال ، تحقق من شكل "وحدة برامج Java القياسية" مع Lombok:

 @Getter @Setter @NoArgsConstructor public class Bean implements Serializable { int firstBeanProperty; String secondBeanProperty; }

كما قد تتخيل ، يتم تجميع الكود أعلاه إلى:

 public class Bean implements Serializable { private int firstBeanProperty; private String secondBeanProperty; public int getFirstBeanProperty() { return this.firstBeanProperty; } public String getSecondBeanProperty() { return this.secondBeanProperty; } public void setFirstBeanProperty(int firstBeanProperty) { this.firstBeanProperty = firstBeanProperty; } public void setSecondBeanProperty(String secondBeanProperty) { this.secondBeanProperty = secondBeanProperty; } public Bean() { } }

لاحظ ، مع ذلك ، أنك ستضطر على الأرجح إلى تثبيت مكون إضافي في حال كنت تنوي استخدام Lombok مع IDE الخاص بك. يمكن العثور على إصدار البرنامج المساعد IntelliJ IDEA هنا.

الخطأ الشائع الثاني: تسريب داخلي

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

 @Entity @NoArgsConstructor @Getter public class TopTalentEntity { @Id @GeneratedValue private Integer id; @Column private String name; public TopTalentEntity(String name) { this.name = name; } }

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

 @AllArgsConstructor @NoArgsConstructor @Getter public class TopTalentData { private String name; }

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

الخطأ الشائع الثالث: عدم فصل المخاوف

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

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

 @RestController public class TopTalentController { private final TopTalentRepository topTalentRepository; @RequestMapping("/toptal/get") public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(this::entityToData) .collect(Collectors.toList()); } private TopTalentData entityToData(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } }

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

 @RestController @RequestMapping("/toptal") @AllArgsConstructor public class TopTalentController { private final TopTalentService topTalentService; @RequestMapping("/get") public List<TopTalentData> getTopTalent() { return topTalentService.getTopTalent(); } } @AllArgsConstructor @Service public class TopTalentService { private final TopTalentRepository topTalentRepository; private final TopTalentEntityConverter topTalentEntityConverter; public List<TopTalentData> getTopTalent() { return topTalentRepository.findAll() .stream() .map(topTalentEntityConverter::toResponse) .collect(Collectors.toList()); } } @Component public class TopTalentEntityConverter { public TopTalentData toResponse(TopTalentEntity topTalentEntity) { return new TopTalentData(topTalentEntity.getName()); } }

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

الخطأ الشائع الرابع: التضارب وضعف التعامل مع الخطأ

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

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

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

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

قد يكون أحد الأمثلة على تنسيق الرد على الخطأ الشائع:

 @Value public class ErrorResponse { private Integer errorCode; private String errorMessage; }

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

الخطأ الشائع # 5: التعامل غير الصحيح مع تعدد مؤشرات الترابط

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

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

تجنب الحالة العالمية

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

تجنب القابلية للتغير

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

تسجيل البيانات الحاسمة

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

إعادة استخدام التطبيقات الحالية

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

الخطأ الشائع رقم 6: عدم استخدام التحقق المستند إلى التعليقات التوضيحية

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

 @RequestMapping("/put") public void addTopTalent(@RequestBody TopTalentData topTalentData) { boolean nameNonExistentOrHasInvalidLength = Optional.ofNullable(topTalentData) .map(TopTalentData::getName) .map(name -> name.length() == 10) .orElse(true); if (nameNonExistentOrInvalidLength) { // throw some exception } topTalentService.addTopTalent(topTalentData); }

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

يمكن تنفيذ ذلك بشكل أكثر نظافة عن طريق استخدام مدقق Hibernate مع Spring. لنقم أولاً بإعادة تشكيل طريقة addTopTalent لدعم التحقق من الصحة:

 @RequestMapping("/put") public void addTopTalent(@Valid @NotNull @RequestBody TopTalentData topTalentData) { topTalentService.addTopTalent(topTalentData); } @ExceptionHandler @ResponseStatus(HttpStatus.BAD_REQUEST) public ErrorResponse handleInvalidTopTalentDataException(MethodArgumentNotValidException methodArgumentNotValidException) { // handle validation exception }

بالإضافة إلى ذلك ، سيتعين علينا الإشارة إلى الخاصية التي نريد التحقق من صحتها في فئة TopTalentData :

 public class TopTalentData { @Length(min = 10, max = 10) @NotNull private String name; }

الآن سيقوم Spring باعتراض الطلب والتحقق من صحته قبل استدعاء الطريقة - ليست هناك حاجة لاستخدام اختبارات يدوية إضافية.

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

 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = { MyAnnotationValidator.class }) public @interface MyAnnotation { String message() default "String length does not match expected"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; int value(); } @Component public class MyAnnotationValidator implements ConstraintValidator<MyAnnotation, String> { private int expectedLength; @Override public void initialize(MyAnnotation myAnnotation) { this.expectedLength = myAnnotation.value(); } @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { return s == null || s.length() == this.expectedLength; } }

لاحظ أنه في هذه الحالات ، تتطلب منك أفضل الممارسات بشأن فصل الاهتمامات وضع علامة على خاصية على أنها صالحة إذا كانت خالية ( s == null ضمن طريقة isValid ) ، ثم استخدم التعليق التوضيحي @NotNull إذا كان هذا مطلبًا إضافيًا لـ خاصية:

 public class TopTalentData { @MyAnnotation(value = 10) @NotNull private String name; }

الخطأ الشائع السابع: (لا يزال) استخدام تكوين قائم على XML

بينما كان XML ضروريًا للإصدارات السابقة من Spring ، يمكن في الوقت الحاضر إجراء معظم التكوين حصريًا عبر كود Java / التعليقات التوضيحية ؛ تشكل تكوينات XML مجرد رمز معياري إضافي وغير ضروري.

تستخدم هذه المقالة (بالإضافة إلى مستودع GitHub المصاحب لها) التعليقات التوضيحية لتكوين Spring and Spring يعرف الفاصوليا التي يجب توصيلها لأن حزمة الجذر قد تم شرحها باستخدام تعليق توضيحي مركب @SpringBootApplication ، مثل:

 @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

التعليق التوضيحي المركب (يمكنك معرفة المزيد عنه في وثائق الربيع يعطي Spring تلميحًا حول الحزم التي يجب مسحها ضوئيًا لاسترداد الحبوب. في حالتنا الملموسة ، هذا يعني أنه سيتم استخدام ما يلي أسفل الحزمة (co.kukurin) العلوية للأسلاك:

  • MyAnnotationValidator ( @Component ، TopTalentConverter )
  • @RestController ( TopTalentController )
  • TopTalentRepository @Repository
  • فئات TopTalentService @Service

إذا كان لدينا أي فئات مشروحة @Configuration إضافية ، فسيتم فحصها أيضًا من أجل التكوين المستند إلى Java.

الخطأ الشائع الثامن: نسيان الملفات الشخصية

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

ضع في اعتبارك الحالة التي تستخدم فيها قاعدة بيانات في الذاكرة من أجل التطوير المحلي ، مع وجود قاعدة بيانات MySQL قيد الإنتاج. يعني هذا ، في جوهره ، أنك ستستخدم عنوان URL مختلفًا و (نأمل) بيانات اعتماد مختلفة للوصول إلى كلٍّ من الاثنين. دعونا نرى كيف يمكن القيام بذلك في ملفي تكوين مختلفين:

application.yaml. ملف

 # set default profile to 'dev' spring.profiles.active: dev # production database details spring.datasource.url: 'jdbc:mysql://localhost:3306/toptal' spring.datasource.username: root spring.datasource.password:

application-dev.yaml. ملف

 spring.datasource.url: 'jdbc:h2:mem:' spring.datasource.platform: h2

من المفترض أنك لن ترغب في تنفيذ أي إجراءات عن طريق الخطأ على قاعدة بيانات الإنتاج الخاصة بك أثناء العبث بالكود ، لذلك من المنطقي تعيين ملف التعريف الافتراضي على dev. على الخادم ، يمكنك بعد ذلك تجاوز ملف تعريف التكوين يدويًا عن طريق توفير a -Dspring.profiles.active=prod إلى JVM. بدلاً من ذلك ، يمكنك أيضًا تعيين متغير بيئة نظام التشغيل الخاص بك على ملف التعريف الافتراضي المطلوب.

الخطأ الشائع التاسع: عدم تبني حقن التبعية

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

 public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController() { this.topTalentService = new TopTalentService(); } }

نحن نسمح لـ Spring بعمل الأسلاك من أجلنا:

 public class TopTalentController { private final TopTalentService topTalentService; public TopTalentController(TopTalentService topTalentService) { this.topTalentService = topTalentService; } }

يشرح محادثة Misko Hevery في Google "أسباب" حقن التبعية بعمق ، لذلك دعونا نرى بدلاً من ذلك كيف يتم استخدامه في الممارسة. في القسم الخاص بفصل الاهتمامات (الأخطاء الشائعة # 3) ، أنشأنا فئة خدمة ووحدة تحكم. لنفترض أننا نريد اختبار وحدة التحكم على افتراض أن TopTalentService يتصرف بشكل صحيح. يمكننا إدراج كائن وهمي بدلاً من تنفيذ الخدمة الفعلي من خلال توفير فئة تكوين منفصلة:

 @Configuration public class SampleUnitTestConfig { @Bean public TopTalentService topTalentService() { TopTalentService topTalentService = Mockito.mock(TopTalentService.class); Mockito.when(topTalentService.getTopTalent()).thenReturn( Stream.of("Mary", "Joel").map(TopTalentData::new).collect(Collectors.toList())); return topTalentService; } }

ثم يمكننا حقن الكائن الوهمي عن طريق إخبار Spring باستخدام SampleUnitTestConfig كمورد التكوين الخاص به:

 @ContextConfiguration(classes = { SampleUnitTestConfig.class })

يتيح لنا ذلك بعد ذلك استخدام تكوين السياق لإدخال وحدة الفول المخصصة في اختبار الوحدة.

الخطأ الشائع # 10: عدم وجود اختبار ، أو اختبار غير لائق

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

عند اختبار خدمات الويب ، نادرًا ما تقوم بإجراء اختبارات وحدة "خالصة" ، نظرًا لأن الاتصال عبر HTTP يتطلب منك عادةً استدعاء Spring's DispatcherServlet ومعرفة ما يحدث عند تلقي HttpServletRequest فعليًا (مما يجعله اختبار تكامل ، والتعامل مع التحقق من الصحة والتسلسل ، إلخ). أثبت REST Assured ، وهو Java DSL للاختبار السهل لخدمات REST ، بالإضافة إلى MockMVC ، أنه يقدم حلاً أنيقًا للغاية. ضع في اعتبارك مقتطف الشفرة التالي مع إدخال التبعية:

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = { Application.class, SampleUnitTestConfig.class }) public class RestAssuredTestDemonstration { @Autowired private TopTalentController topTalentController; @Test public void shouldGetMaryAndJoel() throws Exception { // given MockMvcRequestSpecification givenRestAssuredSpecification = RestAssuredMockMvc.given() .standaloneSetup(topTalentController); // when MockMvcResponse response = givenRestAssuredSpecification.when().get("/toptal/get"); // then response.then().statusCode(200); response.then().body("name", hasItems("Mary", "Joel")); } }

SampleUnitTestConfig تطبيق وهمي لـ TopTalentService في TopTalentController بينما يتم توصيل جميع الفئات الأخرى باستخدام التكوين القياسي المستنتج من حزم المسح الضوئي المتجذرة في حزمة فئة التطبيق. يتم استخدام RestAssuredMockMvc ببساطة لإعداد بيئة خفيفة الوزن وإرسال طلب GET إلى /toptal/get endpoint.

أن تصبح سيد الربيع

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

إذا كنت تبحث عن موارد إضافية ، فإن Spring In Action هو كتاب عملي جيد يغطي العديد من موضوعات الربيع الأساسية.