JavaRush /Blog Java /Random-PL /Testowanie integracji bazy danych przy użyciu MariaDB w c...

Testowanie integracji bazy danych przy użyciu MariaDB w celu zastąpienia MySql

Opublikowano w grupie Random-PL
Testowanie integracji bazy danych przy użyciu MariaDB w celu zastąpienia MySql - 1Dzisiaj chciałbym poruszyć temat testowania, ponieważ im bardziej kod jest objęty testami, tym jest uważany za lepszy i bardziej niezawodny. Porozmawiajmy nie o testach jednostkowych, ale o testach integracyjnych baz danych. Jaka jest dokładnie różnica między testami jednostkowymi a testami integracyjnymi? Testowanie integracji bazy danych przy użyciu MariaDB w celu zastąpienia MySql - 2Modułowe (jednostkowe) to testowanie programu na poziomie poszczególnych modułów, metod lub klas, czyli testy są szybkie i łatwe, wpływając na najbardziej podzielne części funkcjonalności. Nazywa się je również „jednym testem na metodę”. Integracyjne są wolniejsze i cięższe, mogą składać się z kilku modułów i dodatkowych funkcjonalności. Testowanie integracji bazy danych przy użyciu MariaDB w celu zastąpienia MySql - 3Dlaczego testy dla warstwy dao (obiektu dostępu do danych) są testami integracji warstwy? Ponieważ aby przetestować metody z zapytaniami do bazy danych, musimy stworzyć w pamięci RAM osobną bazę danych, zastępującą bazę główną. Pomysł jest taki, że tworzymy potrzebne nam tabele, wypełniamy je danymi testowymi i sprawdzamy poprawność metod klasy repozytorium (w końcu wiemy, jaki powinien być końcowy wynik w danym przypadku). Zacznijmy więc. Tematy dotyczące podłączania bazy danych były od dawna szeroko omawiane, dlatego dzisiaj nie chciałbym się nad tym rozwodzić i rozważymy tylko te części programu, które nas interesują. Domyślnie zaczniemy od tego, że nasza aplikacja oparta jest o Spring Boot, dla warstwy dao Spring JDBC (dla większej przejrzystości) naszą główną bazą danych jest MySQL, a zastąpimy ją za pomocą MariaDB (są maksymalnie kompatybilne, a w związku z tym skrypty MySQL nigdy nie będą powodować konfliktów z dialektem MariaDB, tak jak będzie to miało miejsce w przypadku H2). Warunkowo założymy również, że nasz program wykorzystuje Liquibase do zarządzania i stosowania zmian w schemacie bazy danych, w związku z czym wszystkie zastosowane skrypty są przechowywane w naszej aplikacji.

Struktura projektu

Pokazane są tylko dotknięte części: Testowanie integracji bazy danych przy użyciu MariaDB w celu zastąpienia MySql - 5I tak, dzisiaj stworzymy roboty)) Testowanie integracji bazy danych przy użyciu MariaDB w celu zastąpienia MySql - 6Skrypt dla tabeli, metody, dla których dzisiaj będziemy testować (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;
Podmiot reprezentujący tę tabelę:
@Builder
@Data
public class Robot {

   private Long id;

   private String name;

   private String cpu;

   private String producer;
}
Interfejs dla testowanego repozytorium:
public interface RobotDAO {

   Robot findById(Long id);

   Robot create(Robot robot);

   List<Robot> findAll();

   Robot update(Robot robot);

   void delete(Long id);
}
Właściwie tutaj są standardowe operacje CRUD, bez egzotyki, więc rozważymy wdrożenie nie wszystkich metod (no cóż, nikogo to nie zaskoczy), ale niektórych - dla większej zwięzłości:
@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();
   }
Zróbmy małą dygresję i zobaczmy co się dzieje z naszymi zależnościami (przedstawione są tylko te użyte w demonstrowanej części aplikacji):
<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 - zależność dla samej bazy danych MariaDb 10 - zależność przy połączeniu ze SpringBootem 16 - Lombok (no cóż, chyba każdy wie, co to za biblioteka) 22 - starter do testów (gdzie osadzony jest potrzebny nam JUnit) 28 - starter do praca ze springJdbc Przyjrzyjmy się kontenerowi Spring z fasolami potrzebnymi do naszych testów (w szczególności fasolą do tworzenia 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 - główny komponent do podnoszenia MariaDB (dla aplikacji opartych na Spring Framework) 10 - zdefiniowanie komponentu bazy danych 12 - ustawienie nazwy tworzonej bazy danych 17 - wyciągnięcie konfiguracji dla naszego przypadku 19 - budowanie bazy danych przy użyciu wzorca Builder ( dobry przegląd wzorca ) I wreszcie, całe to zamieszanie dotyczy komponentu bean JdbcTemplate do komunikacji z podnoszoną bazą danych. Pomysł jest taki, że będziemy mieć główną klasę do testów Tao, po której będą dziedziczyć wszystkie klasy testowe Dao, których zadania obejmują:
  1. uruchomienie niektórych skryptów używanych w głównej bazie danych (skrypty do tworzenia tabel, zmiany kolumn i inne);
  2. uruchomienie skryptów testowych wypełniających tabele danymi testowymi;
  3. usuwanie tabel.
@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 - za pomocą adnotacji @SpringBootTest ustawiamy konfigurację testową 11 - jako argument w tej metodzie podajemy nazwy potrzebnych nam tabel, a on jako odpowiedzialny pracowity załaduje je za nas (co daje nam możliwość aby ponownie użyć tej metody tyle, ile dusza zapragnie) 21 - używamy tej metody do czyszczenia, czyli usunięcia wszystkich tabel (i ich danych) z bazy danych 27 - argumentem w tej metodzie jest tablica nazw skryptów z danymi testowymi który zostanie załadowany w celu przetestowania konkretnej metody.Nasz skrypt z danymi testowymi:
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')
A teraz to, na co wszyscy się dzisiaj zebraliśmy.

Zajęcia testujące 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 - dziedziczymy z klasy głównej dla naszych testów 4 - nasze testowane repozytorium 7 - metoda, która będzie uruchamiana przed każdym testem 8 - używamy metody klasy nadrzędnej do ładowania niezbędnych tabel 11 - inicjujemy nasze dao 15 - metoda który będzie uruchamiany po każdym teście, czyszczenie naszej bazy danych 19 - implementacja naszego RowMappera, analogicznie do klasy Tao Używamy @Before i @After, które są używane przed i po jednej metodzie testowej, ale moglibyśmy wziąć jakąś bibliotekę, która nam na to pozwala używać adnotacji związanych z początkiem testów wykonania tej klasy i końcem. Na przykład ten , który znacznie przyspieszyłby testy, ponieważ tabele musiałyby być tworzone i całkowicie usuwane za każdym razem i raz na zajęcia. Ale my tego nie robimy. Dlaczego pytasz? A co jeśli jedna z metod zmieni strukturę tabeli? Na przykład usuń jedną kolumnę. W takim przypadku pozostałe metody mogą albo zawieść, albo muszą odpowiednio zareagować (na przykład utworzyć kolumnę tylną). Trzeba przyznać, że daje nam to niepotrzebne łączenie (zależność) testów od siebie, co nam nie służy. Ale odkopuję, kontynuujmy...

Testowanie metody 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 - wypełnij tabelę danymi testowymi 5 - uzyskaj identyfikator potrzebnej nam jednostki 6 - skorzystaj z testowanej metody 8...12 - porównaj otrzymane dane z oczekiwanymi

Aktualizuj test metody

@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 - wypełnij tabelę danymi testowymi 5 - pobierz identyfikator aktualizowanej jednostki 7 - zbuduj aktualizowaną jednostkę 14 - użyj testowanej metody 15 - pobierz zaktualizowaną jednostkę do weryfikacji 20...28 - porównaj otrzymane dane z oczekiwane Testowanie metody aktualizacji jest podobne do tworzenia. Przynajmniej dla mnie. Możesz dowolnie zmieniać uzgodnienia: czeków nigdy nie jest za dużo. Chciałbym również zaznaczyć, że testy nie gwarantują pełnej funkcjonalności ani braku błędów. Testy zapewniają jedynie, że rzeczywisty wynik programu (jego fragment) odpowiada oczekiwanemu. W takim przypadku sprawdzane są tylko te części, dla których napisano testy.

Ruszamy z zajęciami z testami...

Testowanie integracji bazy danych przy użyciu MariaDB w celu zastąpienia MySql - 7Zwycięstwo)) Chodźmy zrobić herbatę i zjeść ciasteczka: zasługujemy na to)) Testowanie integracji bazy danych przy użyciu MariaDB w celu zastąpienia MySql - 8

Przydatne linki

Tym, którzy skończyli czytać, dziękuję za uwagę i... Testowanie integracji bazy danych przy użyciu MariaDB w celu zastąpienia MySql - 9

*epicka muzyka z Gwiezdnych Wojen*

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