今天我想談談測試,因為測試覆蓋的程式碼越多,就被認為越好、越可靠。我們不談單元測試,談資料庫的整合測試。單元測試和整合測試到底有什麼差別? 模組化(單元)是在單一模組、方法或類別的層級上測試程序,也就是說,測試快速而簡單,影響功能中最可分割的部分。它們也被稱為“每種方法一個測試”。整合的速度較慢且較重,並且可以由多個模組和附加功能組成。 為什麼要進行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 (嗯,我想每個人都知道這是什麼類型的庫) 22 - 測試的啟動器(嵌入了我們需要的JUnit) 28 -啟動器使用 springJdbc 讓我們來看看 Spring 容器,其中包含我們測試所需的 bean(特別是 MariaDB 建立 bean):
@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 框架的應用程式) 10 - 定義資料庫 bean 12 - 設定建立的資料庫的名稱 17 - 為我們的案例提取配置 19 - 使用 Builder 模式建立資料庫(對該模式的一個很好的概述)最後,最值得關注的是用於與所引發的資料庫進行通訊的 JdbcTemplate bean。我們的想法是,我們將有一個用於Tao測試的主類,所有Tao測試類別都將從該類別繼承,其任務包括:
- 啟動主資料庫中使用的一些腳本(用於建立表格、更改列等的腳本);
- 啟動用測試資料填充表格的測試腳本;
- 刪除表。
@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 - 我們從測試的主類別繼承 4 - 我們測試的儲存庫 7 - 將在每次測試之前啟動的方法 8 - 我們使用父類別方法載入必要的表 11 - 我們初始化 dao 15 - 一個方法它將在每次測試後啟動,清理我們的資料庫19 - RowMapper 的實現,類似於Tai 類別我們使用@Before 和@After,它們在一個測試方法之前和之後使用,但我們可以使用一些允許我們使用的庫使用與此類的執行測試的開始和結束相關的註釋。例如,這將顯著加快測試速度,因為每次都必須建立表並完全刪除,每個類別一次。但我們不這樣做。為什麼問?如果其中一種方法更改了表的結構怎麼辦?例如,刪除一列。在這種情況下,其餘方法可能會失敗或必須按預期回應(例如,建立後列)。我們不得不承認,這給我們帶來了測試之間不必要的聯繫(依賴),這對我們來說沒有任何用處。但我離題了,讓我們繼續…
測試 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 - 取得我們需要的實體的 ID 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 - 取得正在更新的實體的 ID 7 - 建置更新的實體 14 - 使用正在測試的方法 15 - 取得更新的實體進行驗證 20...28 - 將接收到的資料與測試更新方法與建立類似。至少對我來說。您可以隨意調整對帳:檢查永遠不會太多。我還想指出,測試並不能保證功能完整或不存在錯誤。測試僅確保程序(其片段)的實際結果與預期結果相對應。在這種情況下,僅檢查那些為其編寫測試的部分。
讓我們啟動一個有測試的課程...
勝利))我們去泡茶,吃餅乾:我們應得的))有用的連結
- 一篇關於該技術堆疊未經審查的部分的好文章。
- 關於 Maven、Spring、MySQL、Hibernate 的有趣系列文章。
- 讓我們回顧一下單元測試。
- 一個有趣的整合測試範例,但使用 PostgreSQL 替代。
- 許多人,不僅在測試中,使用 MariaDB 而不是 MySql。
*史詩般的星際大戰音樂*
GO TO FULL VERSION