JavaRush /مدونة جافا /Random-AR /ما هو Mapstruct وكيفية تكوينه بشكل صحيح لاختبار الوحدة في...
Roman Beekeeper
مستوى

ما هو Mapstruct وكيفية تكوينه بشكل صحيح لاختبار الوحدة في تطبيقات SpringBoot

نشرت في المجموعة

خلفية

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

مابستروكت، الأسئلة الشائعة؟

مولد أكواد لتعيينات الفول السريعة والآمنة. مهمتنا الأولى هي معرفة ما هو Mapstruct ولماذا نحتاج إليه. بشكل عام، يمكنك أن تقرأ عنها على الموقع الرسمي. يوجد في الصفحة الرئيسية للموقع ثلاث إجابات للأسئلة: ما هو؟ لماذا؟ كيف؟ سنحاول القيام بذلك أيضًا:

ما هو؟

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

لماذا؟

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

كيف؟

استخدام التعليقات التوضيحية. نحتاج فقط إلى إنشاء تعليق توضيحي يحتوي على تعليق توضيحي رئيسي لـ Mapper يخبر المكتبة أنه يمكن استخدام الأساليب الموجودة في هذه الواجهة للترجمة من كائن إلى آخر. كما قلت سابقًا عن الطلاب، في حالتنا ستكون واجهة StudentMapper، والتي سيكون لها عدة طرق لنقل البيانات من طبقة إلى أخرى:
public class Student {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentDTO {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}

public class StudentModel {
   private Long id;
   private String firstName;
   private String lastName;
   private Integer age;
}
بالنسبة لهذه الفئات، نقوم بإنشاء مخطط خرائط (فيما يلي ما سنسميه الواجهة، والذي يصف ما نريد نقله وأين):
@Mapper
public interface StudentMapper {
   StudentModel toModel(StudentDTO dto);
   Student toEntity(StudentModel model);
   StudentModel toModel(Student entity);
   StudentDTO toDto(StudentModel model);
}
جمال هذا النهج هو أنه إذا كانت أسماء وأنواع الحقول هي نفسها في فئات مختلفة (كما في حالتنا)، فإن إعدادات Mapstruct كافية لإنشاء التنفيذ اللازم بناءً على واجهة StudentMapper في مرحلة الترجمة، والتي سوف يترجم. لقد أصبح الأمر أكثر وضوحًا بالفعل، أليس كذلك؟ دعنا نذهب أبعد من ذلك ونستخدم مثالًا حقيقيًا لتحليل العمل في تطبيق Spring Boot.

مثال على عمل Spring Boot و Mapstruct

أول شيء نحتاجه هو إنشاء مشروع Spring Boot وإضافة Mapstruct إليه. في هذا الصدد، لدي منظمة في GitHub بها قوالب للمستودعات، وبداية Spring Boot هي واحدة منها. وبناءً عليه نقوم بإنشاء مشروع جديد: ما هو Mapstruct وكيفية تكوينه بشكل صحيح لاختبار الوحدة في تطبيقات SpringBoot.  الجزء 1 - 2وبعد ذلك نحصل على المشروع . نعم أيها الأصدقاء، أعطوا المشروع نجمة إذا وجدتموه مفيدًا، حتى أعلم أنني لا أفعل هذا عبثًا. سنكشف في هذا المشروع عن موقف تلقيته في العمل ووصفته في منشور على قناتي على التليجرام . سأوضح بإيجاز الموقف لأولئك الذين لا يعرفون: عندما نكتب اختبارات لمصممي الخرائط (أي لتطبيقات الواجهة التي تحدثنا عنها سابقًا)، نريد أن تمر الاختبارات في أسرع وقت ممكن. أبسط خيار مع مصممي الخرائط هو استخدام التعليق التوضيحي SpringBootTest عند تشغيل الاختبار، والذي سيلتقط ApplicationContext بالكامل لتطبيق Spring Boot ويحقن مصمم الخرائط المطلوب للاختبار داخل الاختبار. لكن هذا الخيار كثيف الاستخدام للموارد ويستغرق وقتًا أطول بكثير، لذا فهو غير مناسب لنا. نحتاج إلى كتابة اختبار وحدة يقوم ببساطة بإنشاء مخطط الخرائط المطلوب والتحقق من أن أساليبه تعمل تمامًا كما نتوقع. لماذا تحتاج الاختبارات لتعمل بشكل أسرع؟ إذا استغرقت الاختبارات وقتًا طويلاً، فإنها تؤدي إلى إبطاء عملية التطوير بأكملها. وإلى أن تمر الاختبارات على الكود الجديد، لا يمكن اعتبار هذا الكود صحيحًا ولن يتم أخذه للاختبار، مما يعني أنه لن يتم أخذه في الإنتاج مما يعني أن المطور لم يكمل العمل. يبدو الأمر، لماذا أكتب اختبارا لمكتبة لا شك في عملها؟ ومع ذلك، نحن بحاجة إلى كتابة اختبار، لأننا نختبر مدى صحة وصفنا لمصمم الخرائط وما إذا كان يفعل ما نتوقعه. أولاً، لجعل عملنا أسهل، دعونا نضيف Lombok إلى مشروعنا عن طريق إضافة تبعية أخرى إلى pom.xml:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>
في مشروعنا، سنحتاج إلى الانتقال من فئات النموذج (التي تستخدم للعمل مع منطق الأعمال) إلى فئات DTO، التي نستخدمها للتواصل مع العالم الخارجي. في نسختنا المبسطة، سنفترض أن الحقول لا تتغير وأن مصممي الخرائط لدينا سيكونون بسيطين. ولكن، إذا كانت هناك رغبة، فمن الممكن كتابة مقال أكثر تفصيلاً حول كيفية العمل مع Mapstruct، وكيفية تكوينه، وكيفية الاستفادة من فوائده. ولكن بعد ذلك، لأن هذه المقالة ستكون طويلة جدا. لنفترض أن لدينا طالبًا لديه قائمة بالمحاضرات والمحاضرين الذين يحضرهم. لنقم بإنشاء حزمة نموذجية . وبناء على ذلك سوف نقوم بإنشاء نموذج بسيط:
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
محاضراته
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
والمحاضرين
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
وقم بإنشاء حزمة dto بجوار حزمة النموذج :
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

import java.util.List;

@Data
public class StudentDTO {

   private Long id;

   private String name;

   private List<LectureDTO> lectures;

   private List<LecturerDTO> lecturers;
}
محاضرات
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LectureDTO {

   private Long id;

   private String name;
}
والمحاضرين
package com.github.romankh3.templaterepository.springboot.dto;

import lombok.Data;

@Data
public class LecturerDTO {

   private Long id;

   private String name;
}
لنقم الآن بإنشاء مخطط خرائط يترجم مجموعة من نماذج المحاضرات إلى مجموعة من محاضرات DTO. أول شيء يجب فعله هو إضافة Mapstruct إلى المشروع. للقيام بذلك، سوف نستخدم موقعهم الرسمي على الانترنت ، كل شيء موصوف هناك. أي أننا بحاجة إلى إضافة تبعية واحدة ومكون إضافي إلى ذاكرتنا (إذا كانت لديك أسئلة حول ماهية الذاكرة، تفضل، Article1 و Article2 ):
<dependency>
   <groupId>org.mapstruct</groupId>
   <artifactId>mapstruct</artifactId>
   <version>1.4.2.Final</version>
</dependency>
وفي الذاكرة في كتلة <build/>. والتي لم نحصل عليها بعد:
<build>
   <plugins>
       <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.5.1</version>
           <configuration>
               <source>1.8</source>
               <target>1.8</target>
               <annotationProcessorPaths>
                   <path>
                       <groupId>org.mapstruct</groupId>
                       <artifactId>mapstruct-processor</artifactId>
                       <version>1.4.2.Final</version>
                   </path>
               </annotationProcessorPaths>
           </configuration>
       </plugin>
   </plugins>
</build>
بعد ذلك، لنقم بإنشاء حزمة مخطط خرائط بجوار dto و model . استنادًا إلى الفئات التي عرضناها سابقًا، ستحتاج إلى إنشاء خمسة مصممي خرائط آخرين:
  • مصمم الخرائط LectureModel <-> LectureDTO
  • قائمة مصمم الخرائط<LectureModel> <-> قائمة<LectureDTO>
  • مصمم الخرائط LecturerModel <-> LecturerDTO
  • قائمة مصمم الخرائط<LecturerModel> <-> قائمة<LecturerDTO>
  • مخطط StudentModel <-> StudentDTO
يذهب:

LectureMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LecturerDTO dto);
}

LectureListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LectureMapper.class)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}

LecturerMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface LectureMapper {
   LectureDTO toDTO(LectureModel model);

   LectureModel toModel(LectureDTO dto);
}

LecturerListMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LecturerDTO;
import com.github.romankh3.templaterepository.springboot.model.LecturerModel;
import org.mapstruct.Mapper;

import java.util.List;

@Mapper(componentModel = "spring", uses = LecturerMapper.class)
public interface LecturerListMapper {
   List<LecturerModel> toModelList(List<LecturerDTO> dloList);
   List<LecturerDTO> toDTOList(List<LecturerModel> modelList);
}

StudentMapper

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.StudentDTO;
import com.github.romankh3.templaterepository.springboot.model.StudentModel;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
public interface StudentMapper {
   StudentDTO toDTO(StudentModel model);
   StudentModel toModel(StudentDTO dto);
}
تجدر الإشارة بشكل منفصل إلى أننا في مصممي الخرائط نشير إلى مصممي خرائط آخرين. ويتم ذلك من خلال حقل الاستخدامات في التعليق التوضيحي لـ Mapper، كما هو الحال في StudentMapper:
@Mapper(componentModel = "spring", uses = {LectureListMapper.class, LecturerListMapper.class})
نحن هنا نستخدم اثنين من مصممي الخرائط لتعيين قائمة المحاضرات وقائمة المحاضرين بشكل صحيح. نحن الآن بحاجة إلى تجميع التعليمات البرمجية الخاصة بنا ومعرفة ما هو موجود وكيف. يمكن القيام بذلك باستخدام أمر الترجمة النظيفة mvn . ولكن، كما اتضح فيما بعد، عند إنشاء تطبيقات Mapstruct لمصممي الخرائط لدينا، لم تقم تطبيقات مصمم الخرائط بالكتابة فوق الحقول. لماذا؟ اتضح أنه لم يكن من الممكن التقاط شرح البيانات من لومبوك. وكان لا بد من فعل شيء ما... لذلك لدينا قسم جديد في المقال.

ربط لومبوك ومابستروكت

بعد بضع دقائق من البحث، اتضح أننا بحاجة إلى ربط Lombok و Mapstruct بطريقة معينة. توجد معلومات حول هذا الأمر في وثائق Mapstruct . بعد فحص المثال الذي اقترحه المطورون من Mapstruct، فلنقم بتحديث ملف pom.xml الخاص بنا: لنضيف إصدارات منفصلة:
​​<properties>
   <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
   <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
دعونا نضيف التبعية المفقودة:
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok-mapstruct-binding</artifactId>
   <version>${lombok-mapstruct-binding.version}</version>
</dependency>
ودعنا نقوم بتحديث البرنامج الإضافي للمترجم الخاص بنا حتى يتمكن من ربط Lombok و Mapstruct:
<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.5.1</version>
   <configuration>
       <source>1.8</source>
       <target>1.8</target>
       <annotationProcessorPaths>
           <path>
               <groupId>org.mapstruct</groupId>
               <artifactId>mapstruct-processor</artifactId>
               <version>${org.mapstruct.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
               <version>${lombok.version}</version>
           </path>
           <path>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok-mapstruct-binding</artifactId>
               <version>${lombok-mapstruct-binding.version}</version>
           </path>
       </annotationProcessorPaths>
   </configuration>
</plugin>
بعد هذا كل شيء يجب أن ينجح. دعونا نجمع مشروعنا مرة أخرى. ولكن أين يمكنك العثور على الفئات التي أنشأها Mapstruct؟ وهي موجودة في المصادر التي تم إنشاؤها: ${projectDir}/target/generated-sources/annotations/ ما هو Mapstruct وكيفية تكوينه بشكل صحيح لاختبار الوحدة في تطبيقات SpringBoot.  الجزء 1 - 3 الآن بعد أن أصبحنا مستعدين لإدراك خيبة أملي من منشور Mapstruct، فلنحاول إنشاء اختبارات لمصممي الخرائط.

نكتب اختبارات لمصممي الخرائط لدينا

سأقوم بإنشاء اختبار سريع وبسيط من شأنه أن يختبر أحد مصممي الخرائط في الحالة التي نقوم فيها بإنشاء اختبار تكامل ولا تقلق بشأن وقت اكتماله:

LectureMapperTest

package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class LectureMapperTest {

   @Autowired
   private LectureMapper mapperUnderTest;

   @Test
   void shouldProperlyMapModelToDto() {
       //given
       LectureModel model = new LectureModel();
       model.setId(11L);
       model.setName("lecture name");

       //when
       LectureDTO dto = mapperUnderTest.toDTO(model);

       //then
       Assertions.assertNotNull(dto);
       Assertions.assertEquals(model.getId(), dto.getId());
       Assertions.assertEquals(model.getName(), dto.getName());
   }

   @Test
   void shouldProperlyMapDtoToModel() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(11L);
       dto.setName("lecture name");

       //when
       LectureModel model = mapperUnderTest.toModel(dto);

       //then
       Assertions.assertNotNull(model);
       Assertions.assertEquals(dto.getId(), model.getId());
       Assertions.assertEquals(dto.getName(), model.getName());
   }
}
هنا، باستخدام التعليق التوضيحي SpringBootTest، نقوم بتشغيل applicationContext بأكمله ومنه، باستخدام التعليق التوضيحي Autowired، نستخرج الفئة التي نحتاجها للاختبار. من وجهة نظر سرعة وسهولة كتابة الاختبار، فهذا جيد جدًا. تم الاختبار بنجاح، كل شيء على ما يرام. لكننا سنذهب في الاتجاه الآخر ونكتب اختبار وحدة لمصمم الخرائط، على سبيل المثال، LectureListMapper...
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl();

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
نظرًا لأن التطبيقات التي ينشئها Mapstruct هي في نفس فئة مشروعنا، فيمكننا استخدامها بسهولة في اختباراتنا. يبدو كل شيء رائعًا - بدون تعليقات توضيحية، فنحن ننشئ الفصل الذي نحتاجه بأبسط طريقة وهذا كل شيء. ولكن عندما نقوم بإجراء الاختبار، سنفهم أنه سيتعطل وسيكون هناك NullPointerException في وحدة التحكم... وذلك لأن تنفيذ مخطط LectureListMapper يبدو كما يلي:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T21:46:12+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   @Autowired
   private LectureMapper lectureMapper;

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
إذا نظرنا إلى NPE (اختصار لـ NullPointerException)، فسنحصل عليه من المتغيرحاضرة Mapper ، والذي تبين أنه لم تتم تهيئته. لكن في تطبيقنا ليس لدينا مُنشئ يمكننا من خلاله تهيئة المتغير. هذا هو بالضبط السبب وراء قيام Mapstruct بتنفيذ مصمم الخرائط بهذه الطريقة! في الربيع، يمكنك إضافة حبوب إلى الفئات بعدة طرق، يمكنك حقنها من خلال حقل مع التعليق التوضيحي Autowired، كما هو موضح أعلاه، أو يمكنك حقنها من خلال مُنشئ. لقد وجدت نفسي في مثل هذا الموقف الإشكالي في العمل عندما كنت بحاجة إلى تحسين وقت تنفيذ الاختبار. اعتقدت أنه لا يمكن فعل أي شيء حيال ذلك وسكبت ألمي على قناتي على Telegram. ثم ساعدوني في التعليقات وقالوا إنه من الممكن تخصيص استراتيجية الحقن. تحتوي واجهة Mapper على حقل حقنة استراتيجية ، والذي يقبل فقط اسم استراتيجية الحقن ، الذي يحتوي على قيمتين: FIELD و CONSTRUCTOR . الآن، بعد أن عرفنا ذلك، فلنضيف هذا الإعداد إلى مصممي الخرائط لدينا؛ وسأعرضه باستخدام LectureListMapper كمثال :
@Mapper(componentModel = "spring", uses = LectureMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface LectureListMapper {
   List<LectureModel> toModelList(List<LectureDTO> dtos);
   List<LectureDTO> toDTOList(List<LectureModel> models);
}
لقد أبرزت بالخط العريض الجزء الذي أضفته. دعونا نضيف هذا الخيار لجميع الخيارات الأخرى ونعيد ترجمة المشروع بحيث يتم إنشاء مصممي الخرائط بسطر جديد. بمجرد الانتهاء من ذلك، دعونا نرى كيف تغير تنفيذ مخطط LectureListMapper (تم تمييز الجزء الذي نحتاجه بالخط العريض):
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Generated(
   value = "org.mapstruct.ap.MappingProcessor",
   date = "2021-12-09T22:25:37+0300",
   comments = "version: 1.4.2.Final, compiler: javac, environment: Java 15.0.2 (N/A)"
)
@Component
public class LectureListMapperImpl implements LectureListMapper {

   private final LectureMapper lectureMapper;

   @Autowired
   public LectureListMapperImpl(LectureMapper lectureMapper) {

       this.lectureMapper = lectureMapper;
   }

   @Override
   public List<LectureModel> toModelList(List<LectureDTO> dtos) {
       if ( dtos == null ) {
           return null;
       }

       List<LectureModel> list = new ArrayList<LectureModel>( dtos.size() );
       for ( LectureDTO lectureDTO : dtos ) {
           list.add( lectureMapper.toModel( lectureDTO ) );
       }

       return list;
   }

   @Override
   public List<LectureDTO> toDTOList(List<LectureModel> models) {
       if ( models == null ) {
           return null;
       }

       List<LectureDTO> list = new ArrayList<LectureDTO>( models.size() );
       for ( LectureModel lectureModel : models ) {
           list.add( lectureMapper.toDTO( lectureModel ) );
       }

       return list;
   }
}
والآن قامت Mapstruct بتنفيذ حقن مخطط الخرائط من خلال المُنشئ. وهذا بالضبط ما كنا نحاول تحقيقه. الآن سيتوقف اختبارنا عن التجميع، فلنقم بتحديثه ونحصل على:
package com.github.romankh3.templaterepository.springboot.mapper;

import com.github.romankh3.templaterepository.springboot.dto.LectureDTO;
import com.github.romankh3.templaterepository.springboot.model.LectureModel;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.List;

class LectureListMapperTest {

   private final LectureListMapper lectureListMapper = new LectureListMapperImpl(new LectureMapperImpl());

   @Test
   void shouldProperlyMapListDtosToListModels() {
       //given
       LectureDTO dto = new LectureDTO();
       dto.setId(12L);
       dto.setName("I'm BATMAN!");

       List<LectureDTO> dtos = Collections.singletonList(dto);

       //when
       List<LectureModel> models = lectureListMapper.toModelList(dtos);

       //then
       Assertions.assertNotNull(models);
       Assertions.assertEquals(1, models.size());
       Assertions.assertEquals(dto.getId(), models.get(0).getId());
       Assertions.assertEquals(dto.getName(), models.get(0).getName());
   }
}
الآن، إذا أجرينا الاختبار، فسيعمل كل شيء كما هو متوقع، لأننا في LectureListMapperImpl نجتاز LectureMapper الذي يحتاجه... النصر! ليس الأمر صعبًا عليكم، لكني مسرور: أيها الأصدقاء، كل شيء كالمعتاد، اشتركوا في حسابي على GitHub ، في حسابي على Telegram . هناك أنشر نتائج أنشطتي، هناك أشياء مفيدة حقًا) وأدعوك بشكل خاص للانضمام إلى مجموعة المناقشة على قناة التلجرام . يحدث ذلك أنه إذا كان لدى شخص ما سؤال فني، فيمكنه الحصول على إجابة هناك. هذا التنسيق مثير للاهتمام للجميع، يمكنك قراءة من يعرف ماذا واكتساب الخبرة.

خاتمة

كجزء من هذه المقالة، تعرفنا على هذا المنتج الضروري والمستخدم بشكل متكرر مثل Mapstruct. لقد اكتشفنا ما هو ولماذا وكيف. باستخدام مثال حقيقي، شعرنا بما يمكن القيام به وكيف يمكن تغييره. لقد نظرنا أيضًا في كيفية إعداد حقن الفاصوليا من خلال المُنشئ، بحيث يكون من الممكن اختبار مصممي الخرائط بشكل صحيح. سمح الزملاء من Mapstruct لمستخدمي منتجهم باختيار كيفية حقن مصممي الخرائط بالضبط، وهو ما نشكرهم عليه بلا شك. ولكن، على الرغم من حقيقة أن Spring يوصي بحقن الحبوب من خلال المُنشئ، فقد قام شباب Mapstruct بتعيين الحقن عبر الحقل افتراضيًا. لماذا هذا؟ لا اجابة. أظن أنه قد تكون هناك أسباب لا نعرف عنها شيئًا، ولهذا السبب فعلوا ذلك بهذه الطريقة. وللتعرف عليهم، قمت بإنشاء إصدار GitHub في مستودع منتجاتهم الرسمي.
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION