امروز می خواهم در مورد تست صحبت کنم، زیرا هر چه کد بیشتر با تست پوشانده شود، بهتر و قابل اعتمادتر در نظر گرفته می شود. بیایید نه در مورد تست واحد، بلکه در مورد تست یکپارچه سازی پایگاه های داده صحبت کنیم. تفاوت بین تست های واحد و تست های یکپارچه سازی دقیقا چیست؟ ماژولار (واحد) آزمایش یک برنامه در سطح ماژولها، روشها یا کلاسهای فردی است، یعنی تستها سریع و آسان هستند و بر بخشپذیرترین بخشهای عملکرد تأثیر میگذارند. آنها همچنین به عنوان "یک تست در هر روش" نامیده می شوند. ادغامها کندتر و سنگینتر هستند و میتوانند از چندین ماژول و عملکرد اضافی تشکیل شوند. چرا تستهای لایه dao (شیء دسترسی به داده) تست یکپارچهسازی هستند؟ از آنجا که برای آزمایش متدها با پرس و جو در پایگاه داده، باید یک پایگاه داده جداگانه در RAM ایجاد کنیم و جایگزین اصلی کنیم. ایده این است که ما جداول مورد نیاز خود را ایجاد می کنیم، آنها را با داده های آزمایشی پر می کنیم و صحت روش های کلاس مخزن را بررسی می کنیم (به هر حال، ما می دانیم که نتیجه نهایی در یک مورد خاص چه خواهد بود). بنابراین، بیایید شروع کنیم. موضوعات مربوط به اتصال یک پایگاه داده مدت هاست که به طور گسترده پوشش داده شده است، و بنابراین امروز نمی خواهم در این مورد صحبت کنم و فقط بخش هایی از برنامه را که مورد علاقه ما هستند در نظر خواهیم گرفت. به طور پیش فرض، ما از این واقعیت شروع می کنیم که برنامه ما بر اساس 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 bean برای ارتباط با پایگاه داده است که در حال افزایش است. ایده این است که ما یک کلاس اصلی برای تست های تائو خواهیم داشت که تمام کلاس های تست تائو از آن به ارث می برند که وظایف آن عبارتند از:
- راه اندازی برخی از اسکریپت های مورد استفاده در پایگاه داده اصلی (اسکریپت هایی برای ایجاد جداول، تغییر ستون ها و موارد دیگر).
- راه اندازی اسکریپت های آزمایشی که جداول را با داده های آزمایشی پر می کند.
- حذف جداول
@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 یک پیکربندی آزمایشی 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 - ما از کلاس اصلی برای تست های خود ارث می بریم - مخزن تست شده 7 - روشی که قبل از هر تست 8 راه اندازی می شود - از متد کلاس والد برای بارگذاری جداول ضروری استفاده می کنیم 11 - dao 15 خود را مقداردهی اولیه می کنیم - یک روش که بعد از هر آزمایش راه اندازی می شود و پایگاه داده ما را پاک می کند 19 - پیاده سازی RowMapper ما، مشابه کلاس Tao ما از @Before و @After استفاده می کنیم که قبل و بعد از یک روش تست استفاده می شوند، اما می توانیم مقداری lib که به ما اجازه می دهد استفاده کنیم. برای استفاده از حاشیه نویسی گره خورده به ابتدای اجرای آزمایش های این کلاس و پایان. به عنوان مثال، این یکی ، که به طور قابل توجهی سرعت تست ها را افزایش می دهد، زیرا جداول باید هر بار و یک بار در هر کلاس ایجاد و کاملاً حذف شوند. اما ما این کار را نمی کنیم. چرا می پرسی؟ اگر یکی از روش ها ساختار جدول را تغییر دهد چه؟ به عنوان مثال، یک ستون را حذف کنید. در این مورد، روشهای باقیمانده ممکن است شکست بخورند یا باید مطابق انتظار پاسخ دهند (مثلاً یک ستون پشتی ایجاد کنید). باید اعتراف کنیم که این امر به ما ارتباط (وابستگی) غیرضروری تست ها به یکدیگر را می دهد که هیچ فایده ای برای ما ندارد. اما من پرت میشم، ادامه بدیم...
تست متد 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