JavaRush /Blog Java /Random-ES /Pruebas de integración de una base de datos usando MariaD...

Pruebas de integración de una base de datos usando MariaDB para reemplazar MySql

Publicado en el grupo Random-ES
Pruebas de integración de una base de datos usando MariaDB para reemplazar MySql - 1Hoy me gustaría hablar sobre las pruebas, porque cuanto más pruebas se cubren el código, mejor y más confiable se considera. No hablemos de pruebas unitarias, sino de pruebas de integración de bases de datos. ¿Cuál es exactamente la diferencia entre pruebas unitarias y pruebas de integración? Pruebas de integración de una base de datos usando MariaDB para reemplazar MySql - 2Modular (unitario) es probar un programa a nivel de módulos, métodos o clases individuales, es decir, las pruebas son rápidas y sencillas y afectan las partes más divisibles de la funcionalidad. También se les conoce como “una prueba por método”. Los de integración son más lentos y pesados, y pueden constar de varios módulos y funcionalidades adicionales. Pruebas de integración de una base de datos usando MariaDB para reemplazar MySql - 3¿Por qué las pruebas para la integración de la capa dao (objeto de acceso a datos)? Porque para probar métodos con consultas a la base de datos, necesitamos crear una base de datos separada en la RAM, reemplazando la principal. La idea es que creemos las tablas que necesitamos, las llenemos con datos de prueba y verifiquemos la exactitud de los métodos de la clase del repositorio (después de todo, sabemos cuál debería ser el resultado final en un caso determinado). Vamos a empezar. Los temas sobre la conexión de una base de datos se han tratado ampliamente durante mucho tiempo y, por lo tanto, hoy no me gustaría detenerme en esto y consideraremos solo las partes del programa que nos interesan. De forma predeterminada, partiremos del hecho de que nuestra aplicación está basada en Spring Boot, para la capa dao Spring JDBC (para mayor claridad), nuestra base de datos principal es MySQL, y la reemplazaremos usando MariaDB (son máximamente compatibles y en consecuencia, los scripts MySQL nunca tendrán conflictos con el dialecto MariaDB, como los habrá con H2). También asumiremos condicionalmente que nuestro programa usa Liquibase para administrar y aplicar cambios al esquema de la base de datos y, en consecuencia, todos los scripts aplicados se almacenan en nuestra aplicación.

Estructura del proyecto

Solo se muestran las partes afectadas: Pruebas de integración de una base de datos usando MariaDB para reemplazar MySql - 5Y sí, hoy crearemos robots)) Pruebas de integración de una base de datos usando MariaDB para reemplazar MySql - 6Script para la tabla, cuyos métodos probaríamos hoy (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;
La entidad que representa esta tabla:
@Builder
@Data
public class Robot {

   private Long id;

   private String name;

   private String cpu;

   private String producer;
}
Interfaz para el repositorio probado:
public interface RobotDAO {

   Robot findById(Long id);

   Robot create(Robot robot);

   List<Robot> findAll();

   Robot update(Robot robot);

   void delete(Long id);
}
En realidad, aquí hay operaciones CRUD estándar, sin exóticas, por lo que consideraremos la implementación no de todos los métodos (bueno, esto no sorprenderá a nadie), sino de algunos, para mayor brevedad:
@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();
   }
Hagamos una pequeña digresión y veamos qué está pasando con nuestras dependencias (solo se presentan aquellas utilizadas para la parte demostrada de la aplicación):
<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 - dependencia para la base de datos MariaDb en sí 10 - dependencia para conectarse con SpringBoot 16 - Lombok (bueno, creo que todos saben qué tipo de biblioteca es esta) 22 - iniciador para pruebas (donde está integrado el JUnit que necesitamos) 28 - iniciador para trabajando con springJdbc Echemos un vistazo al contenedor Spring con los beans necesarios para nuestras pruebas (en particular, el bean de creación 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 - el componente principal para generar MariaDB (para aplicaciones basadas en Spring Framework) 10 - definir un bean de base de datos 12 - establecer el nombre de la base de datos creada 17 - extraer las configuraciones para nuestro caso 19 - construir una base de datos usando el patrón Builder ( una buena descripción general del patrón ) Y finalmente, de lo que se trata todo este alboroto es del bean JdbcTemplate para la comunicación con la base de datos que se está generando. La idea es que tendremos una clase principal para las pruebas de Tao, de la cual heredarán todas las clases de pruebas de Tao, cuyas tareas incluyen:
  1. ejecutar algunos scripts utilizados en la base de datos principal (scripts para crear tablas, cambiar columnas y otros);
  2. lanzar scripts de prueba que llenan tablas con datos de prueba;
  3. eliminando tablas.
@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 la anotación @SpringBootTest establecemos una configuración de prueba 11 - como argumento en este método pasamos los nombres de las tablas que necesitamos y él, como trabajador responsable, las cargará por nosotros (lo que nos da la oportunidad reutilizar este método tanto como nuestro corazón desee) 21 - usamos este método para limpiar, es decir, eliminar todas las tablas (y sus datos) de la base de datos 27 - el argumento en este método es una matriz de nombres de scripts con datos de prueba que se cargará para probar un método específico. Nuestro script con datos de prueba:
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')
Y ahora lo que nos hemos reunido todos para hoy.

clase de prueba de 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 - heredamos de la clase principal para nuestras pruebas 4 - nuestro repositorio probado 7 - un método que se lanzará antes de cada prueba 8 - usamos el método de la clase principal para cargar las tablas necesarias 11 - inicializamos nuestro dao 15 - un método que se lanzará después de cada prueba, limpiando nuestra base de datos 19 - implementación de nuestro RowMapper, análogo a la clase Tao Usamos @Before y @After, que se usan antes y después de un método de prueba, pero podríamos tomar alguna lib que nos permita utilizar anotaciones vinculadas al inicio de las pruebas de ejecución de esta clase y al final. Por ejemplo, éste , que aceleraría significativamente las pruebas, ya que las tablas tendrían que crearse y eliminarse por completo cada vez y una vez por clase. Pero no hacemos eso. ¿Porque preguntas? ¿Qué pasa si uno de los métodos cambia la estructura de la tabla? Por ejemplo, elimine una columna. En este caso, los métodos restantes pueden fallar o deben responder como se esperaba (por ejemplo, crear una columna posterior). Tenemos que admitir que esto nos da una conexión (dependencia) innecesaria de las pruebas entre sí, lo cual no nos sirve de nada. Pero estoy divagando, sigamos...

Probando el 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 - complete la tabla con datos de prueba 5 - obtenga la identificación de la entidad que necesitamos 6 - use el método que se está probando 8...12 - compare los datos recibidos con los esperados

Prueba del método de actualización

@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 - complete la tabla con datos de prueba 5 - obtenga la identificación de la entidad que se está actualizando 7 - cree la entidad actualizada 14 - use el método que se está probando 15 - obtenga la entidad actualizada para verificación 20...28 - compare los datos recibidos con los esperados Probar el método de actualización es similar a crear. Al menos para mi. Puedes torcer las conciliaciones tanto como quieras: nunca puede haber demasiados controles. También me gustaría señalar que las pruebas no garantizan la funcionalidad completa ni la ausencia de errores. Las pruebas sólo garantizan que el resultado real del programa (su fragmento) corresponda al esperado. En este caso, solo se verifican aquellas partes para las cuales se escribieron las pruebas.

Empecemos una clase con pruebas...

Pruebas de integración de una base de datos usando MariaDB para reemplazar MySql - 7Victoria)) Vamos a preparar té y comprar galletas: nos lo merecemos)) Pruebas de integración de una base de datos usando MariaDB para reemplazar MySql - 8

Enlaces útiles

Para aquellos que han terminado de leer, gracias por su atención y... Pruebas de integración de una base de datos usando MariaDB para reemplazar MySql - 9

*música épica de Star Wars*

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION