這篇要來紀錄使用 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;
}
}
參考資料:
沒有留言 :
張貼留言