Bugun men test haqida gapirmoqchiman, chunki kod qanchalik ko'p testlar bilan qoplangan bo'lsa, u shunchalik yaxshi va ishonchli hisoblanadi. Keling, birlik testi haqida emas, balki ma'lumotlar bazalarining integratsion testlari haqida gapiraylik. Birlik testlari va integratsiya testlari o'rtasidagi aniq farq nima? Modulli (birlik) dasturni individual modullar, usullar yoki sinflar darajasida sinovdan o'tkazadi, ya'ni testlar tez va oson bo'lib, funksionallikning eng bo'linadigan qismlariga ta'sir qiladi. Ular, shuningdek, "har bir usul uchun bitta test" deb ataladi. Integratsiya sekinroq va og'irroq bo'lib, ular bir nechta modul va qo'shimcha funktsiyalardan iborat bo'lishi mumkin. Nima uchun dao (Ma'lumotlarga kirish ob'ekti) qatlami integratsiyasi testlari? Chunki ma'lumotlar bazasiga so'rovlar bilan usullarni sinab ko'rish uchun biz RAMda asosiysini almashtirgan holda alohida ma'lumotlar bazasini yaratishimiz kerak. G'oya shundan iboratki, biz kerakli jadvallarni yaratamiz, ularni test ma'lumotlari bilan to'ldiramiz va omborlar sinfi usullarining to'g'riligini tekshiramiz (oxir-oqibat, biz ma'lum bir holatda yakuniy natija qanday bo'lishi kerakligini bilamiz). Shunday ekan, boshlaylik. Ma'lumotlar bazasini ulash bo'yicha mavzular uzoq vaqtdan beri yoritilgan va shuning uchun bugun men bu haqda to'xtalmoqchi emasman va biz dasturning faqat bizni qiziqtirgan qismlarini ko'rib chiqamiz. Odatiy bo'lib, biz ilovamiz Spring Boot-ga asoslanganligidan boshlaymiz, Spring JDBC dao qatlami uchun (aniqlik uchun), bizning asosiy ma'lumotlar bazasi MySQL bo'lib, biz uni MariaDB yordamida almashtiramiz (ular maksimal darajada mos keladi va va shunga ko'ra MySQL skriptlari hech qachon MariaDB dialekti bilan ziddiyat bo'lmaydi, chunki H2 bilan bo'ladi). Shuningdek, biz shartli ravishda dasturimiz ma'lumotlar bazasi sxemasiga o'zgartirishlarni boshqarish va qo'llash uchun Liquibase-dan foydalanadi va shunga mos ravishda barcha qo'llaniladigan skriptlar bizning ilovamizda saqlanadi deb taxmin qilamiz.
Loyiha tuzilishi
Faqat ta'sirlangan qismlar ko'rsatiladi: Ha, bugun biz robotlar yaratamiz)) Jadval uchun skript, biz bugun sinab ko'radigan usullar (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;
Ushbu jadvalni ifodalovchi ob'ekt:
@Builder
@Data
public class Robot {
private Long id;
private String name;
private String cpu;
private String producer;
}
Sinov qilingan ombor uchun interfeys:
public interface RobotDAO {
Robot findById(Long id);
Robot create(Robot robot);
List<Robot> findAll();
Robot update(Robot robot);
void delete(Long id);
}
Aslida, bu erda ekzotiksiz standart CRUD operatsiyalari mavjud, shuning uchun biz barcha usullarni emas (yaxshi, bu hech kimni ajablantirmaydi) amalga oshirishni ko'rib chiqamiz, lekin ba'zilari - qisqartirish uchun:
@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();
}
Keling, biroz chetga chiqamiz va bog'liqliklarimiz bilan nima sodir bo'layotganini ko'rib chiqamiz (faqat ilovaning ko'rsatilgan qismi uchun ishlatiladiganlar taqdim etiladi):
<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 - MariaDb ma'lumotlar bazasiga bog'liqlik 10 - SpringBoot 16 bilan ulanishga bog'liqlik - Lombok (menimcha, bu qanday lib ekanligini hamma biladi) 22 - sinov uchun starter (bizga kerak bo'lgan JUnit o'rnatilgan) 28 - starter springJdbc bilan ishlash Keling, sinovlarimiz uchun zarur bo'lgan loviya bilan bahor konteynerini ko'rib chiqaylik (xususan, MariaDB yaratish loviya):
@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 - MariaDB ni ko'tarish uchun asosiy komponent (Spring Framework asosidagi ilovalar uchun) 10 - ma'lumotlar bazasini aniqlash 12 - yaratilgan ma'lumotlar bazasi nomini o'rnatish 17 - bizning holatimiz uchun konfiguratsiyalarni chiqarib tashlash 19 - Builder naqshidan foydalanib ma'lumotlar bazasini yaratish ( Naqshning yaxshi umumiy ko'rinishi ) Va nihoyat, barcha shov-shuvlar ko'tarilayotgan ma'lumotlar bazasi bilan aloqa qilish uchun JdbcTemplate loviyasidir. G'oya shundan iboratki, bizda Tao testlari uchun asosiy sinf bo'ladi, undan barcha Tao test sinflari meros qilib olinadi, ularning vazifalariga quyidagilar kiradi:
- asosiy ma'lumotlar bazasida ishlatiladigan ba'zi skriptlarni ishga tushirish (jadvallar yaratish, ustunlarni o'zgartirish va boshqalar uchun skriptlar);
- jadvallarni test ma'lumotlari bilan to'ldiradigan test skriptlarini ishga tushirish;
- jadvallarni o'chirish.
@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 - @SpringBootTest annotatsiyasidan foydalanib, biz test konfiguratsiyasini o'rnatdik 11 - bu usulda argument sifatida biz kerakli jadvallarning nomlarini beramiz va u mas'uliyatli mehnatkash sifatida ularni biz uchun yuklaydi (bu bizga imkoniyat beradi) bu usulni yuragimiz xohlagancha qayta ishlatish) 21 - biz ushbu usulni tozalash uchun foydalanamiz, ya'ni ma'lumotlar bazasidan barcha jadvallarni (va ularning ma'lumotlarini) o'chirish 27 - bu usuldagi argument test ma'lumotlari bilan skriptlar nomlari qatoridir. Bu ma'lum bir usulni sinab ko'rish uchun yuklanadi. Sinov ma'lumotlari bilan bizning skriptimiz:
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')
Va endi biz bugun nima yig'dik.
Tao test klassi
@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 - testlarimiz uchun asosiy sinfdan meros bo'lamiz 4 - sinovdan o'tgan omborimiz 7 - har bir testdan oldin ishga tushiriladigan usul 8 - kerakli jadvallarni yuklash uchun ota-klass usulidan foydalanamiz 11 - dao 15 ni ishga tushiramiz - usul Bu har bir sinovdan so'ng ishga tushiriladi, ma'lumotlar bazasini tozalaydi 19 - Tao sinfiga o'xshash RowMapper dasturini amalga oshirish Biz bir sinov usulidan oldin va keyin ishlatiladigan @Before va @After dan foydalanamiz, lekin biz bizga ruxsat beruvchi ba'zi lib-ni olishimiz mumkin. ushbu toifadagi testlarning boshiga va oxiriga bog'langan izohlardan foydalanish. Misol uchun, bu testlarni sezilarli darajada tezlashtiradi, chunki jadvallar har safar va har bir sinfda bir marta yaratilishi va butunlay o'chirilishi kerak edi. Lekin biz buni qilmaymiz. Nega, deb so'rayapsizmi? Agar usullardan biri jadval tuzilishini o'zgartirsa-chi? Masalan, bitta ustunni o'chiring. Bunday holda, qolgan usullar muvaffaqiyatsiz bo'lishi mumkin yoki kutilganidek javob berishi kerak (masalan, orqa ustunni yaratish). Tan olishimiz kerakki, bu bizga testlarning bir-biriga keraksiz bog'lanishini (qaramligini) beradi, bu bizga hech qanday foyda keltirmaydi. Ammo men chekinaman, keling, davom etaylik ...
findById usulini sinab ko'rish
@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 - jadvalni test ma'lumotlari bilan to'ldirish 5 - bizga kerak bo'lgan ob'ekt identifikatorini olish 6 - tekshirilayotgan usuldan foydalanish 8...12 - olingan ma'lumotlarni kutilganlar bilan solishtirish
Sinov usulini yangilash
@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 - jadvalni test ma'lumotlari bilan to'ldirish 5 - yangilanayotgan ob'ekt identifikatorini olish 7 - yangilangan ob'ektni qurish 14 - sinovdan o'tkazilayotgan usuldan foydalanish 15 - tekshirish uchun yangilangan ob'ektni olish 20...28 - olingan ma'lumotlarni solishtirish. kutilganlar Yangilash usulini sinab ko'rish yaratishga o'xshaydi. Hech bo'lmaganda men uchun. Siz yarashuvlarni xohlaganingizcha burishingiz mumkin: hech qachon juda ko'p tekshiruvlar bo'lishi mumkin emas. Shuni ham ta'kidlashni istardimki, testlar to'liq funksionallikni yoki xatolarning yo'qligini kafolatlamaydi. Sinovlar faqat dasturning haqiqiy natijasi (uning bo'lagi) kutilgan natijaga mos kelishini ta'minlaydi. Bunday holda, faqat testlar yozilgan qismlar tekshiriladi.
Testlar bilan dars boshlaymiz...
G'alaba)) Keling, choy qaynatamiz va pechenye olamiz: biz bunga loyiqmiz))foydali havolalar
- Ushbu texnologiya to'plamining tekshirilmagan qismi haqida yaxshi maqola.
- Maven, Spring, MySQL, Hibernate haqida qiziqarli maqolalar turkumi.
- Keling, birlik testlarida xotiramizni biroz yangilaymiz.
- Integratsiya testining qiziqarli namunasi, ammo PostgreSQL almashtirish bilan.
- Ko'p odamlar, nafaqat testlarda, MySql o'rniga MariaDB dan foydalanadilar.
*yulduzli urushlar epik musiqasi*
GO TO FULL VERSION