Springboot+DBunit單元測試,數據庫隔離測試

關於DBunit

百度百科:dbunit是一個基於junit擴展的數據庫測試框架。
簡言之,dbunit是爲了在單元測試中,通過備份數據庫、導入測試數據、回滾恢復數據庫的手段,在不污染數據庫的前提下完成一系列單元測試工作。

DBunit關鍵知識點

IDataSet接口:用於操作表集合
ITable接口:用於操作表數據集合
DatabaseOperation類:對錶數據執行一系列操作,比如刷新、刪除、插入等

DBunit操作流程

在沒使用DBunit做單元測試之前,一般遵循junit的測試流程:

  1. @BeforeClass在加載測試類之前執行初始化操作
  2. @Before執行單元測試方法前執行操作
  3. @Test執行測試邏輯
  4. @After執行單元測試方法後執行操作
  5. @AfterClass在測試類結束後操作

加入DBunit實際上只是在junit的流程中添加幾步操作數據庫的動作:

  1. @BeforeClass構建連接數據源(DataSource)
  2. @Before創建連接,備份數據並且插入測試數據
  3. @Test執行測試邏輯
  4. @After測試完成還原數據
  5. @AfterClass關閉連接

操作的流程不是固定的,具體還是要根據測試的邏輯來決定,如果能夠加入事務特性進行單元測試可以大大精簡流程

Springboot集成DBunit步驟

進入正題,這裏使用的版本是:

  • DBunit 2.5.3
  • Springboot 2

一、引用依賴


		<!-- SpringBoot 測試 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- dbunit包 -->
		<dependency>
			<groupId>org.dbunit</groupId>
			<artifactId>dbunit</artifactId>
			<version>2.5.3</version>
			<scope>test</scope>
		</dependency>

二、初始化表&創建測試數據

初始化SQL腳本:

CREATE TABLE `db_unit_test` (
  `id` bigint(20) NOT NULL,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

測試數據XML:

<?xml version='1.0' encoding='UTF-8'?>
<dataset>
    <db_unit_test id="1" name="張三"/>
    <db_unit_test id="2" name="李四"/>
    <db_unit_test id="3" name="王五"/>
</dataset>

三、封裝DBunit方法

AbstractBaseTest封裝DBunit基本操作方法,包含初始化連接、關閉連接方法,備份數據、還原數據、導入數據和清空數據方法。其中AbstractBaseTest繼承了AbstractTransactionalJUnit4SpringContextTests類的事務特性,可以方便使用事務回滾特性實現數據庫0污染。
參考:https://www.cnblogs.com/wade-xu/p/4547381.html

/**
 *  <pre>
 * 單元測試基類
 * 封裝DBunit相關操作方法
 * </pre>
 * https://www.cnblogs.com/wade-xu/p/4547381.html
 * @author oyf
 */
@RunWith(SpringJUnit4ClassRunner.class)
@Transactional
//繼承AbstractTransactionalJUnit4SpringContextTests會在方法執行完成後進行事務回滾,如果需要不回滾事務需要在方法上加上 @Rollbak(false)
public abstract class AbstractBaseTest extends AbstractTransactionalJUnit4SpringContextTests {

    @Autowired
    private DataSource dataSource;
    /**
     * 數據庫連接對象
     */
    private static IDatabaseConnection conn;
    /**
     * 備份文件
     */
    private File tempFile;
    /**
     * 文件跟目錄
     */
    public static final String ROOT_URL = "src/test/resources/";

//    DBunit方法  --------------------------------------------------------

    /**
     * 獲取數據庫連接
     * @throws Exception
     */
    @Before
    public void setup() throws Exception {
        //get DataBaseSourceConnection
        conn = new DatabaseConnection(DataSourceUtils.getConnection(dataSource));
    }

    /**
     * 關閉數據庫連接
     * @throws Exception
     */
    //這裏不能用@After,當測試方法有@Rollback(false)註解時會在事務沒有結束之前關閉了數據庫連接
    @AfterTransaction
    public void teardown() throws Exception {
        if (conn != null) {
            conn.close();
        }
    }

    /**
     * Get Query DataSet
     *
     * @Title: getQueryDataSet
     * @return
     * @throws SQLException
     */
    protected QueryDataSet getQueryDataSet() throws SQLException {
        return new QueryDataSet(conn);
    }


    /**
     * 備份表數據
     *
     * @Title: backupCustom
     * @param tableName
     * @throws Exception
     */
    protected void backupCustom(String... tableName){
        try {
            // back up specific files
            QueryDataSet qds = getQueryDataSet();
            for (String str : tableName) {

                qds.addTable(str);
            }
            tempFile = new File(ROOT_URL+"temp.xml");
            FlatXmlDataSet.write(qds, new FileWriter(tempFile), "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 清空表數據,並導入測試數據
     * @throws Exception
     */
    public void importTables(String file){
        try {
            IDataSet dataSet = new FlatXmlDataSetBuilder().build(new File(ROOT_URL+file));
            DatabaseOperation.CLEAN_INSERT.execute(conn, dataSet);
        }  catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滾數據
     *
     * @Title: rollback
     * @throws Exception
     */
    protected void rollback(){
        try {
            // get the temp file
            FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
            builder.setColumnSensing(true);
            IDataSet ds =builder.build(new FileInputStream(tempFile));

            // recover database
            DatabaseOperation.CLEAN_INSERT.execute(conn, ds);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 清空表數據
     *
     * @param tableName
     * @throws Exception
     */
    protected void clearTable(String tableName){
        try {
            DefaultDataSet dataset = new DefaultDataSet();
            dataset.addTable(new DefaultTable(tableName));
            DatabaseOperation.DELETE_ALL.execute(conn, dataset);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

四、準備業務代碼

主要是準備查詢數據用的service和dao,具體代碼可以在https://gitee.com/oumuv/h2TestDemo下載
在這裏插入圖片描述

五、編寫測試類

這裏簡單演示了兩種方式實現數據庫隔離測試,test1()方法用spring的事務特性,test2()使用DBunit手動回滾數據

/**
 * 測試demo
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = H2Application.class)
public class TestDemo2 extends AbstractBaseTest{

    @Autowired
    DBunitService dBunitService;

    /**
     * 使用事務回滾機制,自動還原數據庫
     */
    @Test
    public void test1() {
        //導入測試數據
        importTables("dbunit-data.xml");
        List<DBunitEntity> list = dBunitService.list();
        Assert.assertNotNull(list);
    }

    /**
     * 加上註解 @Rollback(false) 不使用事務回滾,手動還原數據庫
     */
    @Test
    @Rollback(false)
    public void test2() {
        //備份數據,備份數據在resources/temp.xml
        backupCustom("db_unit_test");
        //導入測試數據
        importTables("dbunit-data.xml");
        List<DBunitEntity> list = dBunitService.list();
        Assert.assertNotNull(list);
        //手動恢復數據
        rollback();
    }
}

運行測試用例發現查出來的數據確實是xml中的測試數據,而數據庫中的數據並沒有被污染也沒有缺失,結果便是成功了!

具體代碼可在https://gitee.com/oumuv/h2TestDemo自行下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章