JavaRush /Blog Java /Random-FR /Test d'intégration d'une base de données utilisant MariaD...

Test d'intégration d'une base de données utilisant MariaDB pour remplacer MySql

Publié dans le groupe Random-FR
Test d'intégration d'une base de données utilisant MariaDB pour remplacer MySql - 1Aujourd'hui, je voudrais parler des tests, car plus le code est couvert de tests, plus il est considéré comme meilleur et fiable. Ne parlons pas de tests unitaires, mais de tests d'intégration de bases de données. Quelle est exactement la différence entre les tests unitaires et les tests d’intégration ? Test d'intégration d'une base de données utilisant MariaDB pour remplacer MySql - 2Modulaire (unité) teste un programme au niveau de modules, méthodes ou classes individuels, c'est-à-dire que les tests sont rapides et faciles, affectant les parties les plus divisibles de la fonctionnalité. On les appelle également « un test par méthode ». Ceux d’intégration sont plus lents et plus lourds et peuvent comprendre plusieurs modules et fonctionnalités supplémentaires. Test d'intégration d'une base de données utilisant MariaDB pour remplacer MySql - 3Pourquoi les tests pour la couche dao (Data Access Object) sont-ils des tests d'intégration ? Parce que pour tester les méthodes avec des requêtes sur la base de données, nous devons créer une base de données distincte dans la RAM, en remplacement de la base de données principale. L'idée est que nous créons les tables dont nous avons besoin, les remplissons avec des données de test et vérifions l'exactitude des méthodes de classe du référentiel (après tout, nous savons quel devrait être le résultat final dans un cas donné). Alors, commençons. Les sujets sur la connexion d'une base de données ont longtemps été largement couverts, et c'est pourquoi aujourd'hui je ne voudrais pas m'attarder là-dessus, et nous ne considérerons que les parties du programme qui nous intéressent. Par défaut, nous partirons du fait que notre application est basée sur Spring Boot, pour la couche dao Spring JDBC (pour plus de clarté), notre base de données principale est MySQL, et nous la remplacerons par MariaDB (elles sont compatibles au maximum, et par conséquent, les scripts MySQL n'auront jamais de conflits avec le dialecte MariaDB, comme il y en aura avec H2). Nous supposerons également conditionnellement que notre programme utilise Liquibase pour gérer et appliquer les modifications au schéma de la base de données et, par conséquent, tous les scripts appliqués sont stockés dans notre application.

Structure du projet

Seules les parties concernées sont affichées : Test d'intégration d'une base de données utilisant MariaDB pour remplacer MySql - 5Et oui, aujourd'hui nous allons créer des robots)) Test d'intégration d'une base de données utilisant MariaDB pour remplacer MySql - 6Script pour la table, les méthodes pour lesquelles nous testerions aujourd'hui (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;
L'entité représentant cette table :
@Builder
@Data
public class Robot {

   private Long id;

   private String name;

   private String cpu;

   private String producer;
}
Interface pour le référentiel testé :
public interface RobotDAO {

   Robot findById(Long id);

   Robot create(Robot robot);

   List<Robot> findAll();

   Robot update(Robot robot);

   void delete(Long id);
}
En fait, voici des opérations CRUD standard, sans exotiques, nous considérerons donc la mise en œuvre non pas de toutes les méthodes (enfin, cela ne surprendra personne), mais de certaines - pour plus de concision :
@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();
   }
Faisons une petite digression et voyons ce qui se passe avec nos dépendances (seules celles utilisées pour la partie démontrée de l'application sont présentées) :
<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 - dépendance pour la base de données MariaDb elle-même 10 - dépendance pour la connexion avec SpringBoot 16 - Lombok (enfin, je pense que tout le monde sait de quel type de bibliothèque il s'agit) 22 - démarreur pour les tests (où le JUnit dont nous avons besoin est intégré) 28 - démarreur pour travailler avec springJdbc Jetons un coup d'œil au conteneur Spring avec les beans nécessaires à nos tests (en particulier le bean de création 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 - le composant principal pour élever MariaDB (pour les applications basées sur Spring Framework) 10 - définir un bean de base de données 12 - définir le nom de la base de données créée 17 - extraire les configurations pour notre cas 19 - construire une base de données à l'aide du modèle Builder ( un bon aperçu du modèle ) Et enfin, tout le bruit concerne le bean JdbcTemplate pour la communication avec la base de données en cours de création. L'idée est que nous aurons une classe principale pour les tests Tao, dont hériteront toutes les classes de tests Tao, dont les tâches incluent :
  1. lancer certains scripts utilisés dans la base de données principale (scripts de création de tables, de modification de colonnes et autres) ;
  2. lancer des scripts de test qui remplissent les tableaux avec des données de test ;
  3. suppression de tableaux.
@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 - en utilisant l'annotation @SpringBootTest, nous définissons une configuration de test 11 - comme argument dans cette méthode, nous transmettons les noms des tables dont nous avons besoin, et lui, en tant que travailleur acharné responsable, les chargera pour nous (ce qui nous donne l'opportunité pour réutiliser cette méthode autant que notre cœur le désire) 21 - nous utilisons cette méthode pour nettoyer, à savoir supprimer toutes les tables (et leurs données) de la base de données 27 - l'argument dans cette méthode est un tableau de noms de scripts avec des données de test qui sera chargé pour tester une méthode spécifique. Notre script avec les données de test :
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')
Et maintenant ce pour quoi nous sommes tous rassemblés aujourd'hui.

Cours de test 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 - nous héritons de la classe principale pour nos tests 4 - notre référentiel testé 7 - une méthode qui sera lancée avant chaque test 8 - nous utilisons la méthode de la classe parent pour charger les tables nécessaires 11 - nous initialisons notre dao 15 - une méthode qui sera lancé après chaque test, nettoyant notre base de données 19 - implémentation de notre RowMapper, analogue à la classe Tao Nous utilisons @Before et @After, qui sont utilisés avant et après une méthode de test, mais nous pourrions prendre une lib qui nous permet utiliser des annotations liées au début des tests d'exécutions de cette classe et à la fin. Par exemple celui-ci , qui accélérerait considérablement les tests, puisqu'il faudrait créer et supprimer complètement des tables à chaque fois, et une fois par classe. Mais nous ne le faisons pas. Pourquoi demandes-tu? Que se passe-t-il si l'une des méthodes modifie la structure du tableau ? Par exemple, supprimez une colonne. Dans ce cas, les méthodes restantes peuvent échouer ou doivent répondre comme prévu (par exemple, créer une colonne arrière). Nous devons admettre que cela nous donne une connexion (dépendance) inutile des tests les uns par rapport aux autres, ce qui ne nous est d'aucune utilité. Mais je m'éloigne du sujet, continuons...

Tester la méthode 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 - remplir le tableau avec les données de test 5 - obtenir l'identifiant de l'entité dont nous avons besoin 6 - utiliser la méthode testée 8...12 - comparer les données reçues avec celles attendues

Test de méthode de mise à jour

@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 - remplir le tableau avec les données de test 5 - obtenir l'identifiant de l'entité en cours de mise à jour 7 - construire l'entité mise à jour 14 - utiliser la méthode en cours de test 15 - obtenir l'entité mise à jour pour vérification 20...28 - comparer les données reçues avec ceux attendus Tester la méthode de mise à jour est similaire à créer. Au moins pour moi. Vous pouvez modifier les rapprochements autant que vous le souhaitez : il n'y aura jamais trop de chèques. Je voudrais également noter que les tests ne garantissent pas la pleine fonctionnalité ni l'absence de bugs. Les tests garantissent uniquement que le résultat réel du programme (son fragment) correspond à celui attendu. Dans ce cas, seules les parties pour lesquelles des tests ont été rédigés sont vérifiées.

Commençons un cours avec des tests...

Test d'intégration d'une base de données utilisant MariaDB pour remplacer MySql - 7Victoire)) Allons faire du thé et acheter des cookies : nous le méritons)) Test d'intégration d'une base de données utilisant MariaDB pour remplacer MySql - 8

Liens utiles

Pour ceux qui ont fini de lire, merci de votre attention et... Test d'intégration d'une base de données utilisant MariaDB pour remplacer MySql - 9

*musique épique de Star Wars*

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