![Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 3](https://cdn.javarush.com/images/article/6270633c-1cfa-4b46-aefc-4e5802ab06bd/1024.jpeg)
Struttura del progetto
Vengono mostrate solo le parti interessate:![Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 5](https://cdn.javarush.com/images/article/261272f7-bc93-4174-ad23-5e6dec4c1162/1024.jpeg)
![Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 6](https://cdn.javarush.com/images/article/8d8d0ffa-8898-454b-a100-d3e31ef73e73/1024.jpeg)
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:
- avviare alcuni script utilizzati nel database principale (script per creare tabelle, modificare colonne e altro);
- lanciare script di test che riempiono le tabelle con i dati di test;
- 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 - 7](https://cdn.javarush.com/images/article/de92a3ca-b8d0-4851-845f-9319f30828d8/original.jpeg)
![Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 8](https://cdn.javarush.com/images/article/7cdd0ce2-ea39-4e8b-b499-522b2afb0fa0/original.jpeg)
link utili
- Un buon articolo sulla parte non esaminata di questo stack tecnologico.
- Un'interessante serie di articoli su Maven, Spring, MySQL, Hibernate.
- Rinfresciamo un po' la memoria sugli unit test.
- Un interessante esempio di test di integrazione, ma con sostituzione PostgreSQL.
- Molte persone, e non solo nei test, usano MariaDB invece di MySql.
![Test di integrazione di un database utilizzando MariaDB per sostituire MySql - 9](https://cdn.javarush.com/images/article/151b0636-70a0-46c3-80e0-8307327ef4dc/1024.jpeg)
*musica epica di Star Wars*
GO TO FULL VERSION