Бүгін мен тестілеу туралы айтқым келеді, өйткені code тесттермен неғұрлым көп қамтылса, соғұрлым ол жақсырақ және сенімдірек деп саналады. Бірлік тестілеу туралы емес, деректер қорын интеграциялық тестілеу туралы сөйлесейік. Бірлік сынақтары мен интеграциялық сынақтардың нақты айырмашылығы неде? Модульдік (бірлік) - бұл бағдарламаны жеке модульдер, әдістер немесе сыныптар деңгейінде тестілеу, яғни тесттер функционалдылықтың ең бөлінетін бөліктеріне әсер ететін жылдам және оңай. Олар сондай-ақ «әр әдіске бір сынақ» деп аталады. Интеграциялар баяу және ауырырақ және бірнеше модульдерден және қосымша функциялардан тұруы мүмкін. Неліктен dao (Data Access Object) деңгейіне арналған сынақтар біріктіру сынақтары болып табылады? Өйткені мәліметтер базасына сұраныстары бар әдістерді сынау үшін біз негізгісін ауыстыра отырып, жедел жадта жеке мәліметтер қорын көтеруіміз керек. Идея мынада: біз өзімізге қажетті кестелерді жасаймыз, оларды сынақ деректерімен толтырамыз және репозиторий класс әдістерінің дұрыстығын тексереміз (ақыр соңында, біз белгілі бір жағдайда соңғы нәтиже қандай болуы керек екенін білеміз). Сонымен, бастайық. Мәліметтер қорын қосу тақырыптары бұрыннан бері қарастырылған, сондықтан мен бүгін бұл туралы тоқталғым келмейді және біз бағдарламаның бізді қызықтыратын бөліктерін ғана қарастырамыз. Әдепкі бойынша, біз қосымшаның Spring Boot-қа негізделгенінен бастаймыз, Spring JDBC dao қабаты үшін (анық болу үшін), біздің негізгі дерекқорымыз MySQL болып табылады және біз оны MariaDB арқылы ауыстырамыз (олар барынша үйлесімді және сәйкес MySQL сценарийлері ешқашан MariaDB диалектімен қайшылықтар болмайды, өйткені H2-де болады). Сондай-ақ біз шартты түрде бағдарламамыз дерекқор схемасына өзгертулерді басқару және қолдану үшін Liquibase пайдаланады деп есептейміз және сәйкесінше барлық қолданбалы сценарийлер біздің қолданбада сақталады.
Жоба құрылымы
Тек зардап шеккен бөліктер көрсетіледі: Иә, бүгін біз роботтарды жасаймыз)) Кестеге арналған сценарий, біз бүгін сынайтын әдістер (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;
Осы кестені көрсететін нысан:
@Builder
@Data
public class Robot {
private Long id;
private String name;
private String cpu;
private String producer;
}
Тексерілген репозиторийге арналған интерфейс:
public interface RobotDAO {
Robot findById(Long id);
Robot create(Robot robot);
List<Robot> findAll();
Robot update(Robot robot);
void delete(Long id);
}
Шын мәнінде, мұнда экзотикасыз стандартты CRUD операциялары бар, сондықтан біз барлық әдістерді емес (жақсы, бұл ешкімді таң қалдырмайды), бірақ кейбіреулерін қысқарту үшін қарастырамыз:
@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();
}
Кішкене шегініс жасап, тәуелділіктерімізбен не болып жатқанын көрейік (қолданбаның көрсетілген бөлігі үшін ғана қолданылатындар ұсынылған):
<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 дерекқорының өзіне тәуелділік 10 - SpringBoot 16-ға қосылуға тәуелділік - Lombok (бұл қандай lib екенін бәрі біледі деп ойлаймын) 22 - сынақтарға арналған стартер (бізге қажет JUnit ендірілген жерде) 28 - стартер springJdbc-пен жұмыс істеу Біздің сынақтарымызға қажет бұршақтары бар көктемгі контейнерді қарастырайық (атап айтқанда, 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 - MariaDB-ны көтеруге арналған негізгі компонент (Spring Framework негізіндегі қолданбалар үшін) 10 - деректер қорын анықтау 12 - құрылған Дерекқордың атын орнату 17 - біздің жағдайымыз үшін конфигурацияларды шығару 19 - Builder үлгісін пайдаланып деректер базасын құру ( Үлгіге жақсы шолу ) Және, ақырында, барлық әбігерге түсетін нәрсе - JdbcTemplate бұршағындағы дерекқормен байланыс орнату. Идея мынада: бізде Дао сынақтары үшін негізгі сынып болады, одан барлық Дао сынақ сыныптары мұрагер болады, оның міндеттеріне мыналар кіреді:
- негізгі дерекқорда қолданылатын кейбір сценарийлерді іске қосу (кестелерді құру, бағандарды өзгерту және т.б. сценарийлер);
- кестелерді тест деректерімен толтыратын тест сценарийлерін іске қосу;
- кестелерді жою.
@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 annotationсын қолдана отырып, біз сынақ конфигурациясын орнаттық 11 - бұл әдісте дәлел ретінде біз қажетті кестелердің атауларын береміз және ол жауапты еңбекқор қызметкер ретінде оларды біз үшін жүктейді (бұл бізге мүмкіндік береді бұл әдісті жүрегіміз қалағанша қайта пайдалану) 21 - біз бұл әдісті тазалау үшін қолданамыз, атап айтқанда, деректер базасынан барлық кестелерді (және олардың деректерін) жою 27 - бұл әдістегі аргумент сынақ деректері бар сценарийлер атауларының массиві болып табылады. ол белгілі бір әдісті сынау үшін жүктеледі. Сынақ деректері бар біздің сценарий:
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')
Ал енді бүгін барлығымыз нені жинадық.
Дао тестілеу сабағы
@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 - біз сынақтар үшін негізгі сыныптан мұра аламыз 4 - сынақтан өткен репозиторийіміз 7 - әрбір сынақ алдында іске қосылатын әдіс 8 - қажетті кестелерді жүктеу үшін ата-аналық сынып әдісін қолданамыз 11 - дао 15 - әдісті инициализациялаймыз ол әрбір сынақтан кейін іске қосылады, біздің дерекқорымызды тазартады 19 - RowMapper-ді іске асыру, Tao класына ұқсас Біз @Before және @After пайдаланамыз, олар бір сынақ әдісіне дейін және кейін пайдаланылады, бірақ бізге мүмкіндік беретін кейбір lib-ті алуға болады. осы сыныптың орындалу сынақтарының басына және соңына байланысты annotationларды пайдалану. Мысалы, бұл сынақтарды айтарлықтай жылдамдатады, өйткені кестелер әр уақытта және әр сыныпта бір рет жасалып, толығымен жойылуы керек еді. Бірақ біз мұны істемейміз. Сен неге сұрайсың? Егер әдістердің бірі кестенің құрылымын өзгертсе ше? Мысалы, бір бағанды жойыңыз. Бұл жағдайда қалған әдістер сәтсіз болуы мүмкін немесе күткендей жауап беруі керек (мысалы, артқы баған жасаңыз). Бұл бізге сынақтардың бір-біріне қажетсіз байланысын (тәуелділігін) беретінін мойындауымыз керек, бұл бізге пайдасы жоқ. Бірақ мен шегінемін, әрі қарай жалғастырайық...
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 - кестені сынақ деректерімен толтыру 5 - бізге қажет нысанның идентификаторын алу 6 - тексерілетін әдісті қолдану 8...12 - алынған мәліметтерді күтілгендермен салыстыру
Жаңарту әдісі тесті
@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 - кестені сынақ деректерімен толтыру 5 - жаңартылатын нысанның идентификаторын алу 7 - жаңартылған нысанды құру 14 - тексерілетін әдісті пайдалану 15 - тексеру үшін жаңартылған нысанды алу 20...28 - алынған деректерді күтілетіндер Жаңарту әдісін сынау жасау әдісіне ұқсас. Кем дегенде мен үшін. Сәйкестендірулерді қалағаныңызша бұра аласыз: ешқашан тым көп тексерулер болмайды. Сондай-ақ, сынақтар толық функционалдылыққа немесе қателердің болмауына кепілдік бермейтінін атап өткім келеді. Тесттер тек бағдарламаның нақты нәтижесінің (оның фрагменті) күтілетін нәтижеге сәйкес келетінін ғана қамтамасыз етеді. Бұл жағдайда сынақтар жазылған бөліктер ғана тексеріледі.
Сабақты тесттерден бастайық...
Жеңіс)) Шай қайнатып печенье алайық: біз оған лайықпыз))пайдалы сілтемелер
- Бұл технология стекінің зерттелмеген бөлігі туралы жақсы мақала.
- Maven, Spring, MySQL, Hibernate туралы қызықты мақалалар сериясы.
- Бірлік сынақтары бойынша жадымызды аздап сергітейік.
- Интеграциялық тестілеудің қызықты мысалы, бірақ PostgreSQL ауыстыруымен.
- Көптеген адамдар, тек сынақтарда ғана емес, MySql орнына MariaDB пайдаланады.
*жұлдызды соғыстардың эпикалық музыкасы*
GO TO FULL VERSION