JavaRush /Java Blog /Random-KO /MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트

MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트

Random-KO 그룹에 게시되었습니다
MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트 - 1오늘 저는 테스트에 대해 이야기하고 싶습니다. 왜냐하면 코드에 테스트가 많이 포함될수록 코드가 더 좋고 안정적이라고 간주되기 때문입니다. 단위 테스트가 아니라 데이터베이스 통합 테스트에 대해 이야기하겠습니다. 단위 테스트와 통합 테스트의 차이점은 정확히 무엇입니까? MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트 - 2모듈식(단위)은 개별 모듈, 메소드 또는 클래스 수준에서 프로그램을 테스트합니다. 즉, 테스트는 빠르고 쉬우며 기능의 가장 분할 가능한 부분에 영향을 미칩니다. "메서드당 하나의 테스트"라고도 합니다. 통합은 더 느리고 무겁고 여러 모듈과 추가 기능으로 구성될 수 있습니다. MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트 - 3dao(Data Access Object) 계층 통합 테스트에 대한 테스트가 필요한 이유는 무엇입니까? 데이터베이스에 대한 쿼리로 메서드를 테스트하려면 RAM에 별도의 데이터베이스를 생성하여 기본 데이터베이스를 대체해야 합니다. 아이디어는 필요한 테이블을 생성하고, 테스트 데이터로 채우고, 저장소 클래스 메소드의 정확성을 확인하는 것입니다(결국 우리는 주어진 경우에 최종 결과가 무엇인지 알고 있습니다). 그럼 시작해 보겠습니다. 데이터베이스 연결에 관한 주제는 오랫동안 광범위하게 다루어져 왔기 때문에 오늘은 이에 대해 자세히 다루고 싶지 않으며 프로그램에서 관심 있는 부분만 고려해 보겠습니다. 기본적으로 우리 애플리케이션이 Spring Boot를 기반으로 한다는 사실부터 시작하겠습니다. Spring JDBC dao 계층의 경우(명확성을 높이기 위해) 기본 데이터베이스는 MySQL이고 이를 MariaDB를 사용하여 대체할 것입니다(최대한 호환 가능하며, 따라서 MySQL 스크립트는 H2와 마찬가지로 MariaDB 방언과 충돌이 발생하지 않습니다. 또한 우리 프로그램이 Liquibase를 사용하여 데이터베이스 스키마에 대한 변경 사항을 관리하고 적용하며 이에 따라 적용된 모든 스크립트가 애플리케이션에 저장된다고 조건부로 가정합니다.

프로젝트 구조

영향을 받는 부분만 표시됩니다. MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트 - 5그리고 예, 오늘은 로봇을 생성하겠습니다.)) MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트 - 6오늘 테스트할 방법인 테이블에 대한 스크립트(create_table_robots.sql):
CREATE TABLE `robots`
(
   `id`   BIGINT(20) NOT NULL AUTO_INCREMENT,
   `name` CHAR(255) CHARACTER SET utf8 NOT NULL,
   `cpu`  CHAR(255) CHARACTER SET utf8 NOT NULL,
   `producer`  CHAR(255) CHARACTER SET utf8 NOT NULL,
   PRIMARY KEY (`id`)
) ENGINE = InnoDB
 DEFAULT CHARSET = utf8;
이 테이블을 나타내는 엔터티:
@Builder
@Data
public class Robot {

   private Long id;

   private String name;

   private String cpu;

   private String producer;
}
테스트된 저장소의 인터페이스:
public interface RobotDAO {

   Robot findById(Long id);

   Robot create(Robot robot);

   List<Robot> findAll();

   Robot update(Robot robot);

   void delete(Long id);
}
실제로 여기에는 외래종이없는 표준 CRUD 작업이 있으므로 모든 방법의 구현을 고려할 것입니다 (글쎄, 이것은 누구에게도 놀라지 않을 것입니다). 그러나 더 간결하게 하기 위해 일부 방법의 구현을 고려할 것입니다.
@Repository
@AllArgsConstructor
public class RobotDAOImpl implements RobotDAO {

   private static final String FIND_BY_ID = "SELECT id, name, cpu, producer FROM robots WHERE id = ?";

   private static final String UPDATE_BY_ID = "UPDATE robots SET name = ?, cpu = ?, producer = ?  WHERE id = ?";

   @Autowired
   private final JdbcTemplate jdbcTemplate;

   @Override
   public Robot findById(Long id) {
       return jdbcTemplate.queryForObject(FIND_BY_ID, robotMapper(), id);
   }

   @Override
   public Robot update(Robot robot) {
       jdbcTemplate.update(UPDATE_BY_ID,
               robot.getName(),
               robot.getCpu(),
               robot.getProducer(),
               robot.getId());

       return robot;
   }

   private RowMapper<Robot> robotMapper() {
       return (rs, rowNum) ->
               Robot.builder()
                       .id(rs.getLong("id"))
                       .name(rs.getString("name"))
                       .cpu(rs.getString("cpu"))
                       .producer(rs.getString("producer"))
                       .build();
   }
약간의 여담을 가지고 우리의 종속성에 무슨 일이 일어나고 있는지 살펴보겠습니다(애플리케이션의 시연된 부분에 사용된 것만 제시됩니다).
<dependencies>
   <dependency>
       <groupId>org.mariadb.jdbc</groupId>
       <artifactId>mariadb-java-client</artifactId>
       <version>2.5.2</version>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.craftercms.mariaDB4j</groupId>
       <artifactId>mariaDB4j-springboot</artifactId>
       <version>2.4.2.3</version>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.10</version>
       <scope>provided</scope>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <version>2.2.1.RELEASE</version>
       <scope>test</scope>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jdbc</artifactId>
       <version>2.2.1.RELEASE</version>
   </dependency>
</dependencies>
4 - MariaDb 데이터베이스 자체에 대한 종속성 10 - SpringBoot와의 연결을 위한 종속성 16 - Lombok(글쎄, 이것이 어떤 종류의 lib인지 모두가 알고 있다고 생각합니다) 22 - 테스트를 위한 스타터(우리에게 필요한 JUnit이 내장되어 있는 곳) 28 - 위한 스타터 springJdbc로 작업하기 테스트에 필요한 빈(특히 MariaDB 생성 빈)이 포함된 Spring 컨테이너를 살펴보겠습니다.
@Configuration
public class TestConfigDB {

   @Bean
   public MariaDB4jSpringService mariaDB4jSpringService() {
       return new MariaDB4jSpringService();
   }

   @Bean
   public DataSource dataSource(MariaDB4jSpringService mariaDB4jSpringService) {
       try {
           mariaDB4jSpringService.getDB().createDB("testDB");
       } catch (ManagedProcessException e) {
         e.printStackTrace();
       }

       DBConfigurationBuilder config = mariaDB4jSpringService.getConfiguration();

       return DataSourceBuilder
               .create()
               .username("root")
               .password("root")
               .url(config.getURL("testDB"))
               .driverClassName("org.mariadb.jdbc.Driver")
               .build();
   }

   @Bean
   public JdbcTemplate jdbcTemplate(DataSource dataSource) {
       return new JdbcTemplate(dataSource);
   }
}
5 - MariaDB를 키우기 위한 주요 구성 요소(Spring Framework 기반 애플리케이션용) 10 - 데이터베이스 빈 정의 12 - 생성된 데이터베이스의 이름 설정 17 - 사례에 대한 구성 가져오기 19 - 빌더 패턴을 사용하여 데이터베이스 구축 ( 패턴에 대한 좋은 개요 ) 그리고 마지막으로 모든 소란은 제기되는 데이터베이스와의 통신을 위한 JdbcTemplate 빈에 관한 것입니다. 아이디어는 모든 Dao 테스트 클래스가 상속할 Tao 테스트용 메인 클래스를 갖게 된다는 것입니다. 해당 클래스의 작업에는 다음이 포함됩니다.
  1. 기본 데이터베이스에서 사용되는 일부 스크립트 실행(테이블 생성, 열 변경 등을 위한 스크립트)
  2. 테스트 데이터로 테이블을 채우는 테스트 스크립트 실행
  3. 테이블 삭제.
@SpringBootTest(classes = TestConfigDB.class)
public abstract class DataBaseIT {

   @Autowired
   private JdbcTemplate jdbcTemplate;

   public JdbcTemplate getJdbcTemplate() {
       return jdbcTemplate;
   }

   public void fillDataBase(String[] initList) {
       for (String x : initList) {
           try {
               jdbcTemplate.update(IOUtils.resourceToString("/db.migrations/" + x, StandardCharsets.UTF_8));
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }

   public void cleanDataBase() {
       getJdbcTemplate().update("DROP database testDB");
       getJdbcTemplate().update("CREATE database testDB");
       getJdbcTemplate().update("USE testDB");
   }

   public void fillTables(String[] fillList) {
       for (String x : fillList) {
           try {
               Stream.of(
                       IOUtils.resourceToString("/fill_scripts/" + x, StandardCharsets.UTF_8))
                       .forEach(jdbcTemplate::update);
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }
}
1 - @SpringBootTest 주석을 사용하여 테스트 구성을 설정합니다. 11 - 이 메서드의 인수로 우리가 필요한 테이블의 이름을 전달하고 책임감 있는 열심히 일하는 사람인 그는 우리를 위해 테이블을 로드합니다(이는 우리에게 기회를 제공합니다) 이 방법을 원하는 만큼 재사용합니다) 21 - 데이터베이스에서 모든 테이블(및 해당 데이터)을 삭제하는 등의 작업에 이 방법을 사용합니다. 27 - 이 방법의 인수는 테스트 데이터가 있는 스크립트 이름의 배열입니다. 특정 방법을 테스트하기 위해 로드될 테스트 데이터가 포함된 스크립트:
INSERT INTO robots(name, cpu, producer)
VALUES ('Rex', 'Intel Core i5-9400F', 'Vietnam'),
      ('Molly', 'AMD Ryzen 7 2700X', 'China'),
      ('Ross', 'Intel Core i7-9700K', 'Malaysia')
그리고 이제 오늘 우리 모두가 모은 것입니다.

타오 테스트 수업

@RunWith(SpringRunner.class)
public class RobotDataBaseIT extends DataBaseIT {

   private static RobotDAO countryDAO;

   @Before
   public void fillData() {
       fillDataBase(new String[]{
               "create_table_robots.sql"
       });
       countryDAO = new RobotDAOImpl(getJdbcTemplate());
   }

   @After
   public void clean() {
       cleanDataBase();
   }

   private RowMapper<Robot> robotMapper() {
       return (rs, rowNum) ->
               Robot.builder()
                       .id(rs.getLong("id"))
                       .name(rs.getString("name"))
                       .cpu(rs.getString("cpu"))
                       .producer(rs.getString("producer"))
                       .build();
   }
2 - 테스트를 위해 메인 클래스에서 상속합니다. 4 - 테스트된 저장소 7 - 각 테스트 전에 실행될 메서드 8 - 부모 클래스 메서드를 사용하여 필요한 테이블을 로드합니다. 11 - dao를 초기화합니다. 15 - 메서드 각 테스트 후에 시작되어 데이터베이스를 정리합니다. 19 - Tao 클래스와 유사한 RowMapper 구현 우리는 하나의 테스트 메서드 전후에 사용되는 @Before 및 @After를 사용하지만 우리를 허용하는 일부 lib를 사용할 수 있습니다. 이 클래스의 실행 테스트 시작과 끝에 연결된 주석을 사용합니다. 예를 들어, 매번 , 클래스당 한 번씩 테이블을 생성하고 완전히 삭제해야 하므로 테스트 속도가 크게 향상됩니다. 하지만 우리는 그렇게 하지 않습니다. 왜요? 방법 중 하나가 테이블 구조를 변경하면 어떻게 되나요? 예를 들어 열 하나를 삭제합니다. 이 경우 나머지 메서드는 실패할 수 있거나 예상대로 응답해야 합니다(예: 백 열 생성). 이것이 우리에게 불필요한 테스트 연결(의존)을 제공하고, 이는 우리에게 아무 소용이 없다는 점을 인정해야 합니다. 하지만 난 빗나가서 계속하자...

findById 메소드 테스트

@Test
public void findByIdTest() {
   fillTables(new String[]{"fill_table_robots.sql"});

   Long id = getJdbcTemplate().queryForObject("SELECT id FROM robots WHERE name = 'Molly'", Long.class);
   Robot robot = countryDAO.findById(id);

   assertThat(robot).isNotNull();
   assertThat(robot.getId()).isEqualTo(id);
   assertThat(robot.getName()).isEqualTo("Molly");
   assertThat(robot.getCpu()).isEqualTo("AMD Ryzen 7 2700X");
   assertThat(robot.getProducer()).isEqualTo("China");
}
3 - 테스트 데이터로 테이블 채우기 5 - 필요한 엔터티에 대한 ID 가져오기 6 - 테스트 중인 메서드 사용 8...12 - 수신된 데이터를 예상 데이터와 비교

업데이트 방법 테스트

@Test
public void updateTest() {
   fillTables(new String[]{"fill_table_robots.sql"});

   Long robotId = getJdbcTemplate().queryForObject("SELECT id FROM robots WHERE name = 'Rex'", Long.class);

   Robot updateRobot = Robot.builder()
           .id(robotId)
           .name("Aslan")
           .cpu("Intel Core i5-3470")
           .producer("Narnia")
           .build();

   Robot responseRobot = countryDAO.update(updateRobot);
   Robot updatedRobot = getJdbcTemplate().queryForObject(
           "SELECT id, name, cpu, producer FROM robots WHERE id = ?",
           robotMapper(),
           robotId);

   assertThat(updatedRobot).isNotNull();
   assertThat(updateRobot.getName()).isEqualTo(responseRobot.getName());
   assertThat(updateRobot.getName()).isEqualTo(updatedRobot.getName());
   assertThat(updateRobot.getCpu()).isEqualTo(responseRobot.getCpu());
   assertThat(updateRobot.getCpu()).isEqualTo(updatedRobot.getCpu());
   assertThat(updateRobot.getProducer()).isEqualTo(responseRobot.getProducer());
   assertThat(updateRobot.getProducer()).isEqualTo(updatedRobot.getProducer());
   assertThat(responseRobot.getId()).isEqualTo(updatedRobot.getId());
   assertThat(updateRobot.getId()).isEqualTo(updatedRobot.getId());
}
3 - 테스트 데이터로 테이블 채우기 5 - 업데이트되는 엔터티의 ID 가져오기 7 - 업데이트된 엔터티 구축 14 - 테스트 중인 메서드 사용 15 - 확인을 위해 업데이트된 엔터티 가져오기 20...28 - 수신된 데이터를 다음과 비교 업데이트 방법을 테스트하는 것은 create와 유사합니다. 적어도 나에게는. 원하는 만큼 조정을 비틀 수 있습니다. 수표가 너무 많아서는 안 됩니다. 또한 테스트가 완전한 기능이나 버그의 부재를 보장하지 않는다는 점도 지적하고 싶습니다. 테스트는 프로그램의 실제 결과(해당 부분)가 예상 결과와 일치하는지 확인하는 것뿐입니다. 이 경우 테스트가 작성된 부분만 확인됩니다.

테스트를 통해 수업을 시작하자...

MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트 - 7승리)) 차를 끓이고 쿠키를 사러 가자: 우리는 그럴 자격이 있다)) MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트 - 8

유용한 링크

끝까지 읽어주신 분들의 많은 관심과 참여 부탁드립니다... MySql을 대체하기 위해 MariaDB를 사용한 데이터베이스 통합 테스트 - 9

*서사시적인 스타워즈 음악*

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION