這篇要來紀錄使用 JUnit5 Runner + TestNg + Dabase Rider 進行 Database 測試的方法。
首先,用 JUnit5 來跑 TestNg 的方法可以參考這篇 使用 JUnit5 同時跑 JUnit5 和 TestNg 的 Test Case,只要使用 testng-engine 就可以了。
再來,因為 Database Rider (可以先參考之前的 使用 DBUnit 進行 Database 測試 和 使用 Dabase Rider 進行 Database 測試 這兩篇) 的 Annotation (註解,例如 @DBUnit、@Dataset 這些) 是用 @ExtendWith(DBUnitExtension.class) 的方式以 Extension 來跟 JUnit5協作,
但 TestNg 是看不懂這些 Database Rider 的 Annotation 的,
所以我們要用如 DBUnit 最普通的方式那樣,
自己用程式碼執行的方式 (就是不用 Annotation)來進行 DataSet 的建立、寫資料進 Database 等操作。
下面直接示範程式碼:
Maven Dependency 的 pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>hugo</groupId> <artifactId>junittest</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <maven.compiler.target>11</maven.compiler.target> <maven.compiler.source>11</maven.compiler.source> </properties> <build> <plugins> <!-- Maven 執行 test 的 plugin,surefire 多用於單元測試 (unit test), failsafe 多用於整合測試 (integration test) --> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>3.1.2</version> </plugin> <plugin> <artifactId>maven-failsafe-plugin</artifactId> <version>3.1.2</version> </plugin> </plugins> </build> <dependencyManagement> <dependencies> <dependency> <groupId>org.junit</groupId> <artifactId>junit-bom</artifactId> <version>5.10.3</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.testng/testng --> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>7.10.2</version> <scope>test</scope> </dependency> <!-- 使用 testng-engine 讓 JUnit5 可以跑 TestNg 的 Test Case --> <!-- https://mvnrepository.com/artifact/org.junit.support/testng-engine --> <dependency> <groupId>org.junit.support</groupId> <artifactId>testng-engine</artifactId> <version>1.0.5</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/com.github.database-rider/rider-junit5 --> <dependency> <groupId>com.github.database-rider</groupId> <artifactId>rider-junit5</artifactId> <version>1.42.0</version> <scope>test</scope> </dependency> <!-- Database Reider 輸出 XLSX Dataset 需要 poi-ooxml --> <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.3.0</version> </dependency> </dependencies> </project>
TestNgDatabaseRiderTest.java:
package junittest; import java.io.File; import java.net.URL; import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import org.dbunit.DatabaseUnitException; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeTest; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.github.database.rider.core.api.dataset.DataSetFormat; import com.github.database.rider.core.api.exporter.DataSetExportConfig; import com.github.database.rider.core.configuration.DBUnitConfig; import com.github.database.rider.core.configuration.DataSetConfig; import com.github.database.rider.core.dsl.RiderDSL; import com.github.database.rider.core.exporter.DataSetExporter; public class TestNgDatabaseRiderTest { static final String testResourceFolderPath = ""; static final String backupDatasetResourcePath = "/backupDataset.xlsx"; static final String testDatasetResourcePath = "/testDataset.xlsx"; static final String jdbcDriverName = "org.postgresql.Driver"; static final String databaseUrl = "jdbc:postgresql://localhost:5432/xxxDatabase"; static final String databaseUsername = "username"; static final String databasePassword = "password"; static final String[] includeTableList = new String[] {"xxxTable1", "xxxTable2", "xxxTable3"}; static String[] statementsForExecuteAfterDataSet = new String[]{"SELECT setval('xxxTable1_xxxField_seq', (SELECT coalesce(MAX(xxxField), 0) + 1 FROM xxxTable1), false)", "SELECT setval('xxxTable2_xxxField_seq', (SELECT coalesce(MAX(xxxField), 0) + 1 FROM xxxTable2), false)"}; @BeforeTest static void beforeAll() { //在所有 Test Case 執行之前先備份當前要用到的 Database Tables, //例如 export 成 XLSX 檔案 try { URL backupDatasetResourceFolderUrl = TestNgDatabaseRiderTest.class.getClassLoader().getResource(testResourceFolderPath); File backupDatasetResourceFolder = Paths.get(backupDatasetResourceFolderUrl.toURI()).toFile(); String backupDatasetResourceFilePath = backupDatasetResourceFolder.getAbsolutePath() + backupDatasetResourcePath.replace("/", File.separator); DataSetExporter.getInstance().export(getConnection(), new DataSetExportConfig() .dataSetFormat(DataSetFormat.XLSX) //可以設定只要 Export 某些 Table .includeTables(includeTableList) .queryList(new String[] {}) .outputFileName(backupDatasetResourceFilePath)); } catch (Exception e) { e.printStackTrace(); } } @BeforeMethod //在每一次的 Test Case 執行之前把 Test Dataset 檔案內容匯入當下 Database 中 void beforeEach() throws DatabaseUnitException, SQLException { System.out.println("before each"); try (Connection conn = getConnection();) { RiderDSL.withConnection(conn) .withDataSetConfig(new DataSetConfig(testResourceFolderPath + testDatasetResourcePath) //實測 executeStatementsBefore 和 executeStatementsAfter 效果不好, // executeStatementsBefore 會在 DataSet 塞入 Database 前被執行,但我想要在 DataSet 塞入 Database 後執行。 // executeStatementsAfter 我目前測不出來什麼時候才會被執行,可能我理解有誤? //.executeStatementsBefore("") //.executeStatementsAfter("") ) .withDBUnitConfig(new DBUnitConfig() .cacheConnection(false) //這個很重要,因為預設是 true,有可能會造成不預期的意外,例如使用了 cache 的 connection 但不知 connection 可能早被 close 了 .addDBUnitProperty("escapePattern", "\"?\"") .addDBUnitProperty("caseSensitiveTableNames", true) .addDBUnitProperty("allowEmptyFields", true)) .createDataSet(); } //你可以在這指定一些需要執行的前置做業 Sql 語句給 executeStatementsBefore, //例如使用 PostgreSQL 時,DatabaseOperation.CLEAN_INSERT.execute() 會把 //遞增欄位的遞增值重設成 1,造成之後測試 insert 時發生問題 (id 重覆等之類的), //這時就會需要在這做例如如下 Sql 語句來設置正確的遞增值: //SELECT setval('xxxTable_xxxField_seq', (SELECT coalesce(MAX(xxxField), 0) + 1 FROM xxxTable), false) for (String statement : statementsForExecuteAfterDataSet) { try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(statement);) { try (ResultSet rs = pstmt.executeQuery();) { } } catch (SQLException e) { Assert.fail("Exception occured: " + getExceptionDetail(e)); } } } @AfterMethod void afterEach() { } @AfterTest //在所有 Test Case 測試完後,用之前 Export 出來的 Backup Dataset 匯回去到 Database 中 static void afterAll() throws DatabaseUnitException, SQLException { System.out.println("afterAll"); try (Connection conn = getConnection();) { RiderDSL.withConnection(conn) .withDataSetConfig(new DataSetConfig(testResourceFolderPath + backupDatasetResourcePath) .executeStatementsBefore("SELECT setval('xxxTable1_xxxField_seq', (SELECT coalesce(MAX(xxxField), 0) + 1 FROM xxxTable1), false)", "SELECT setval('xxxTable2_xxxField_seq', (SELECT coalesce(MAX(xxxField), 0) + 1 FROM xxxTable2), false)")) .withDBUnitConfig(new DBUnitConfig() .cacheConnection(false) //這個很重要,因為預設是 true,有可能會造成不預期的意外,例如使用了 cache 的 connection 但不知 connection 可能早被 close 了 .addDBUnitProperty("escapePattern", "\"?\"") .addDBUnitProperty("caseSensitiveTableNames", true) .addDBUnitProperty("allowEmptyFields", true)) .createDataSet(); } for (String statement : statementsForExecuteAfterDataSet) { try (Connection conn = getConnection(); PreparedStatement pstmt = conn.prepareStatement(statement);) { try (ResultSet rs = pstmt.executeQuery();) { } } catch (SQLException e) { Assert.fail("Exception occured: " + getExceptionDetail(e)); } } } //TestNg 可以使用 dependsOnMethods 來設定要在哪個 Test methods 之後才執行 @Test(dependsOnMethods = {"test2"}) void test() { assertEquals(1, 1, "Should be equal."); System.out.println("TestNg: test1"); } @Test void test2() { assertEquals(1, 1, "Should be equal."); System.out.println("TestNg: test2"); } static Connection getConnection() { Connection connection = null; try { Class.forName(jdbcDriverName); connection = DriverManager.getConnection(databaseUrl, databaseUsername, databasePassword); } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } return connection; } }
參考資料:
沒有留言 :
張貼留言