JavaRush /Java Blog /Random-IT /Test di integrazione di un database utilizzando MariaDB p...

Test di integrazione di un database utilizzando MariaDB per sostituire MySql

Pubblicato nel gruppo Random-IT
Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 1Oggi vorrei parlare di testing, perché più il codice è ricoperto di test, più viene considerato migliore e più affidabile. Non parliamo di unit test, ma di test di integrazione dei database. Qual è esattamente la differenza tra test unitari e test di integrazione? Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 2Modulare (unità) sta testando un programma a livello di singoli moduli, metodi o classi, ovvero i test sono rapidi e semplici e interessano le parti più divisibili della funzionalità. Vengono anche definiti “un test per metodo”. Quelli di integrazione sono più lenti e pesanti e possono essere costituiti da diversi moduli e funzionalità aggiuntive. Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 3Perché i test per il livello dao (Data Access Object) sono test di integrazione? Perché per testare i metodi con query al database, dobbiamo creare un database separato nella RAM, sostituendo quello principale. L'idea è creare le tabelle di cui abbiamo bisogno, riempirle con i dati di test e verificare la correttezza dei metodi della classe del repository (dopo tutto, sappiamo quale dovrebbe essere il risultato finale in un determinato caso). Quindi, cominciamo. Gli argomenti sulla connessione di un database sono stati a lungo trattati in lungo e in largo, e quindi oggi non vorrei soffermarmi su questo, e prenderemo in considerazione solo le parti del programma che ci interessano. Per impostazione predefinita, inizieremo dal fatto che la nostra applicazione è basata su Spring Boot, per il layer dao Spring JDBC (per maggiore chiarezza), il nostro database principale è MySQL e lo sostituiremo utilizzando MariaDB (sono massimamente compatibili e di conseguenza gli script MySQL non ci saranno mai conflitti con il dialetto MariaDB, come succederà con H2). Assumeremo anche in modo condizionale che il nostro programma utilizzi Liquibase per gestire e applicare modifiche allo schema del database e, di conseguenza, tutti gli script applicati vengono archiviati nella nostra applicazione.

Struttura del progetto

Vengono mostrate solo le parti interessate: Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 5E sì, oggi creeremo robots)) Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 6Script per la tabella, i metodi per i quali testeremo oggi (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à che rappresenta questa tabella:
@Builder
@Data
public class Robot {

   private Long id;

   private String name;

   private String cpu;

   private String producer;
}
Interfaccia per il repository testato:
public interface RobotDAO {

   Robot findById(Long id);

   Robot create(Robot robot);

   List<Robot> findAll();

   Robot update(Robot robot);

   void delete(Long id);
}
In realtà, qui ci sono operazioni CRUD standard, senza esotici, quindi considereremo l'implementazione non di tutti i metodi (beh, questo non sorprenderà nessuno), ma di alcuni - per maggiore brevità:
@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();
   }
Facciamo una piccola digressione e vediamo cosa succede con le nostre dipendenze (vengono presentate solo quelle utilizzate per la parte dimostrata dell'applicazione):
<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 - dipendenza per il database MariaDb stesso 10 - dipendenza per la connessione con SpringBoot 16 - Lombok (beh, penso che tutti sappiano di che tipo di libreria si tratta) 22 - antipasto per i test (dove è incorporata la JUnit di cui abbiamo bisogno) 28 - antipasto per lavorare con springJdbc Diamo un'occhiata al contenitore Spring con i bean necessari per i nostri test (in particolare, il bean di creazione 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 - il componente principale per generare MariaDB (per applicazioni basate sullo Spring Framework) 10 - definire un bean di database 12 - impostare il nome del database creato 17 - estrarre le configurazioni per il nostro caso 19 - costruire un database utilizzando il pattern Builder ( una buona panoramica del pattern ) E infine, ciò che riguarda tutto il trambusto è il bean JdbcTemplate per la comunicazione con il database che viene generato. L'idea è che avremo una classe principale per i test Tao, da cui erediteranno tutte le classi di test Tao, i cui compiti includono:
  1. avviare alcuni script utilizzati nel database principale (script per creare tabelle, modificare colonne e altro);
  2. lanciare script di test che riempiono le tabelle con i dati di test;
  3. eliminazione delle tabelle.
@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 - utilizzando l'annotazione @SpringBootTest impostiamo una configurazione di test 11 - come argomento in questo metodo passiamo i nomi delle tabelle di cui abbiamo bisogno e lui, come gran lavoratore responsabile, li caricherà per noi (il che ci dà l'opportunità riutilizzare questo metodo quanto il nostro cuore desidera) 21 - utilizziamo questo metodo per pulire, ovvero eliminare tutte le tabelle (e i relativi dati) dal database 27 - l'argomento in questo metodo è un array di nomi di script con dati di test che verrà caricato per testare un metodo specifico. Il nostro script con i dati di 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')
E ora cosa abbiamo raccolto tutti oggi.

Lezione di test del 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 - ereditiamo dalla classe principale per i nostri test 4 - il nostro repository testato 7 - un metodo che verrà lanciato prima di ogni test 8 - utilizziamo il metodo della classe genitore per caricare le tabelle necessarie 11 - inizializziamo il nostro dao 15 - un metodo che verrà lanciato dopo ogni test, ripulendo il nostro database 19 - implementazione del nostro RowMapper, analogo alla classe Tao Usiamo @Before e @After, che vengono usate prima e dopo un metodo di test, ma potremmo prendere qualche lib che ci permetta utilizzare annotazioni legate all'inizio delle esecuzioni dei test di questa classe e alla fine. Ad esempio, questo , che velocizzerebbe notevolmente i test, poiché le tabelle dovrebbero essere create e cancellate completamente ogni volta, e una volta per classe. Ma non lo facciamo. Perchè lo chiedi? Cosa succede se uno dei metodi modifica la struttura della tabella? Ad esempio, elimina una colonna. In questo caso, i metodi rimanenti potrebbero non riuscire o rispondere come previsto (ad esempio, creare una colonna posteriore). Dobbiamo ammettere che questo ci dà una connessione (dipendenza) non necessaria dei test gli uni dagli altri, il che non ci è di alcuna utilità. Ma sto divagando, continuiamo...

Testare il metodo 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 - riempi la tabella con i dati del test 5 - ottieni l'ID dell'entità di cui abbiamo bisogno 6 - usa il metodo in fase di test 8...12 - confronta i dati ricevuti con quelli attesi

Prova del metodo di aggiornamento

@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 - riempire la tabella con i dati di test 5 - ottenere l'id dell'entità da aggiornare 7 - creare l'entità aggiornata 14 - utilizzare il metodo da testare 15 - ottenere l'entità aggiornata per la verifica 20...28 - confrontare i dati ricevuti con quelli attesi Testare il metodo update è simile a create. Almeno per me. Puoi stravolgere le riconciliazioni quanto vuoi: i controlli non sono mai troppi. Vorrei inoltre sottolineare che i test non garantiscono la piena funzionalità o l'assenza di bug. I test assicurano solo che il risultato effettivo del programma (il suo frammento) corrisponda a quello previsto. In questo caso vengono controllate solo le parti per le quali sono stati scritti i test.

Iniziamo una lezione con i test...

Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 7Vittoria)) Andiamo a preparare il tè e prendiamo i biscotti: ce lo meritiamo)) Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 8

link utili

Per chi ha finito di leggere, grazie per l'attenzione e... Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 9

*musica epica di Star Wars*

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