JavaRush /Blogue Java /Random-PT /Teste de integração de um banco de dados usando MariaDB p...

Teste de integração de um banco de dados usando MariaDB para substituir MySql

Publicado no grupo Random-PT
Teste de integração de banco de dados utilizando MariaDB em substituição ao MySql - 1Hoje gostaria de falar sobre testes, pois quanto mais o código é coberto de testes, melhor e mais confiável ele é considerado. Não vamos falar sobre testes unitários, mas sobre testes de integração de bancos de dados. Qual é exatamente a diferença entre testes unitários e testes de integração? Teste de integração de um banco de dados utilizando MariaDB em substituição ao MySql - 2Modular (unidade) é testar um programa no nível de módulos, métodos ou classes individuais, ou seja, os testes são rápidos e fáceis, afetando as partes mais divisíveis da funcionalidade. Eles também são chamados de “um teste por método”. Os de integração são mais lentos e pesados ​​e podem consistir em vários módulos e funcionalidades adicionais. Teste de integração de um banco de dados usando MariaDB em substituição ao MySql - 3Por que os testes para a integração da camada dao (Data Access Object) são testes? Pois para testar métodos com consultas ao banco de dados, precisamos levantar um banco de dados separado na RAM, substituindo o principal. A ideia é criarmos as tabelas que precisamos, preenchê-las com dados de teste e verificar a exatidão dos métodos da classe do repositório (afinal, sabemos qual deve ser o resultado final em um determinado caso). Então, vamos começar. Os tópicos sobre como conectar um banco de dados já foram amplamente abordados e, portanto, hoje não gostaria de me alongar sobre isso e consideraremos apenas as partes do programa que nos interessam. Por padrão, partiremos do fato de que nossa aplicação é baseada em Spring Boot, para a camada Spring JDBC dao (para maior clareza), nosso banco de dados principal é MySQL, e iremos substituí-lo por MariaDB (eles são maximamente compatíveis, e consequentemente, os scripts MySQL nunca haverá conflitos com o dialeto MariaDB, como haverá com H2). Também assumiremos condicionalmente que nosso programa usa Liquibase para gerenciar e aplicar alterações no esquema do banco de dados e, consequentemente, todos os scripts aplicados são armazenados em nosso aplicativo.

Estrutura do projeto

Apenas as partes afetadas são mostradas: Teste de integração de um banco de dados utilizando MariaDB em substituição ao MySql - 5E sim, hoje criaremos robôs)) Teste de integração de um banco de dados usando MariaDB em substituição ao MySql - 6Script para a tabela, os métodos que testaríamos hoje (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;
A entidade que representa esta tabela:
@Builder
@Data
public class Robot {

   private Long id;

   private String name;

   private String cpu;

   private String producer;
}
Interface para o repositório testado:
public interface RobotDAO {

   Robot findById(Long id);

   Robot create(Robot robot);

   List<Robot> findAll();

   Robot update(Robot robot);

   void delete(Long id);
}
Na verdade, aqui estão as operações CRUD padrão, sem exóticos, então consideraremos a implementação não de todos os métodos (bem, isso não surpreenderá ninguém), mas de alguns - para maior brevidade:
@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();
   }
Vamos fazer uma pequena digressão e ver o que está acontecendo com nossas dependências (são apresentadas apenas aquelas usadas para a parte demonstrada da aplicação):
<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 - dependência do próprio banco de dados MariaDb 10 - dependência para conexão com SpringBoot 16 - Lombok (bem, acho que todo mundo sabe que tipo de lib é essa) 22 - iniciador para testes (onde o JUnit que precisamos está incorporado) 28 - iniciador para trabalhando com springJdbc Vamos dar uma olhada no contêiner Spring com os beans necessários para nossos testes (em particular, o bean de criação MariaDB):
@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 - o principal componente para criar o MariaDB (para aplicações baseadas no Spring Framework) 10 - definir um bean de banco de dados 12 - definir o nome do banco de dados criado 17 - retirar as configurações para nosso caso 19 - construir um banco de dados usando o padrão Builder ( uma boa visão geral do padrão ) E, finalmente, o que está acontecendo é o bean JdbcTemplate para comunicação com o banco de dados que está sendo gerado. A ideia é que teremos uma classe principal para testes Tao, da qual herdarão todas as classes de testes Tao, cujas tarefas incluem:
  1. lançar alguns scripts utilizados no banco de dados principal (scripts para criação de tabelas, alteração de colunas e outros);
  2. lançar scripts de teste que preenchem tabelas com dados de teste;
  3. exclusão de tabelas.
@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 - usando a anotação @SpringBootTest definimos uma configuração de teste 11 - como argumento neste método passamos os nomes das tabelas que precisamos, e ele, como trabalhador responsável, irá carregá-las para nós (o que nos dá a oportunidade reutilizar este método tanto quanto nosso coração desejar) 21 - usamos este método para limpeza, ou seja, deletar todas as tabelas (e seus dados) do banco de dados 27 - o argumento neste método é um array de nomes de scripts com dados de teste que será carregado para testar um método específico. Nosso script com dados de teste:
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')
E agora o que todos nós reunimos hoje.

Aula de teste Tao

@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 - herdamos da classe principal para nossos testes 4 - nosso repositório testado 7 - um método que será lançado antes de cada teste 8 - usamos o método da classe pai para carregar as tabelas necessárias 11 - inicializamos nosso dao 15 - um método que será lançado após cada teste, limpando nosso banco de dados 19 - implementação do nosso RowMapper, análogo à classe Tao Usamos @Before e @After, que são usados ​​antes e depois de um método de teste, mas poderíamos pegar alguma lib que nos permita utilizar anotações vinculadas ao início das execuções dos testes desta classe e ao final. Por exemplo, este , que agilizaria significativamente os testes, pois as tabelas teriam que ser criadas e totalmente excluídas todas as vezes e uma vez por aula. Mas não fazemos isso. Porque você pergunta? E se um dos métodos alterar a estrutura da tabela? Por exemplo, exclua uma coluna. Nesse caso, os métodos restantes podem falhar ou responder conforme o esperado (por exemplo, criar uma coluna anterior). Temos que admitir que isso nos dá uma conexão (dependência) desnecessária de testes entre si, o que não nos serve de nada. Mas estou divagando, vamos continuar...

Testando o método 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 - preencher a tabela com dados de teste 5 - obter o id da entidade que precisamos 6 - usar o método que está sendo testado 8...12 - comparar os dados recebidos com os esperados

Teste do método de atualização

@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 - preencher a tabela com dados de teste 5 - obter o id da entidade que está sendo atualizada 7 - construir a entidade atualizada 14 - usar o método que está sendo testado 15 - obter a entidade atualizada para verificação 20...28 - comparar os dados recebidos com os esperados Testar o método de atualização é semelhante a criar. Pelo menos para mim. Você pode distorcer as reconciliações o quanto quiser: nunca pode haver verificações demais. Gostaria também de observar que os testes não garantem funcionalidade total ou ausência de bugs. Os testes apenas garantem que o resultado real do programa (seu fragmento) corresponda ao esperado. Neste caso, apenas as partes para as quais os testes foram escritos são verificadas.

Vamos lançar uma aula com testes...

Teste de integração de um banco de dados usando MariaDB em substituição ao MySql - 7Vitória)) Vamos fazer chá e pegar biscoitos: merecemos)) Teste de integração de um banco de dados utilizando MariaDB em substituição ao MySql - 8

Links Úteis

Para quem já terminou de ler, obrigado pela atenção e... Teste de integração de um banco de dados utilizando MariaDB em substituição ao MySql - 9

*música épica de Star Wars*

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION