一. 假设一个如下需求并编写测试代码示例
测试对象工程中有一个DRBDBManager的java类中封装了数据库连接的创建方法,销毁方法,发行SQL的方法,事务回滚方法,事务提交方法;被测试的对象代码有cdi和非cdi两种类,这两种类中都利用DRBDBManager类实现了对oracle数据库表的更新操作(由于Dao层使用了mbatis所以是通过传参方式把SQL ID给DRBDBManager对象的)。
现在我们对上面的两种类分别进行Junit5的测试代码的编写,并且cdi类以service为例,非cdi类以servlet为例;注意不是基于spring框架,可以使用Cdi unit和DB unit,并使用excel文件来分别保存测试中DB unit使用的测试所需的初始数据和验证数据。
在测试类中使用数据库事务来实现,在测试开始时开启一个事务,在测试结束时回滚事务,从而确保测试期间对oracle数据库表所做的任何修改都不会永久保存,并且确保不会发生锁表导致测试代码不能结束。
根据这个需求,用下面内容展示如何使用JUnit 5、CdiUnit和DBUnit来编写测试代码,其中包括事务管理以确保测试不会对数据库造成持久影响。首先,我们需要创建两个测试类:一个用于CDI类(Service层)的测试,另一个用于非CDI类(Servlet层)的测试。
1. 添加依赖
首先,我们需要在项目的pom.xml文件中添加必要的依赖:
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.7.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oneandone</groupId>
<artifactId>cdiunit</artifactId>
<version>5.1.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>19.8.0.0</version>
<scope>test</scope>
</dependency>
<!-- Add other necessary dependencies here -->
</dependencies>
2. 创建测试类
A. CDI 类(Service层)测试
import org.dbunit.DatabaseTestCase;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import com.oneandone.cdiunit.CdiRunner;
import javax.inject.Inject;
import javax.transaction.UserTransaction;
@ExtendWith(CdiRunner.class)
public class ServiceTest extends DatabaseTestCase {
@Inject
private YourServiceClass service;
@Inject
private UserTransaction transaction;
@Override
protected IDataSet getDataSet() throws Exception {
return new XlsDataSet(new FileInputStream("src/test/resources/initialData.xls"));
}
@Override
protected DatabaseOperation getSetUpOperation() {
return DatabaseOperation.CLEAN_INSERT;
}
@Override
protected DatabaseOperation getTearDownOperation() {
return DatabaseOperation.NONE;
}
@Test
public void testUpdateMethod() throws Exception {
transaction.begin();
try {
service.updateDataMethod(); // 假设这是更新数据的方法
assertExpectedResults(); // 自定义验证方法
} finally {
transaction.rollback();
}
}
private void assertExpectedResults() throws Exception {
// 验证数据库状态是否符合预期
IDataSet expectedDataSet = new XlsDataSet(new FileInputStream("src/test/resources/expectedData.xls"));
// 使用DBUnit的断言方法验证数据库和Excel文件中的期望值
DatabaseAssertion.assertEquals(expectedDataSet, getConnection().createDataSet());
}
}
B. 非CDI 类(Servlet层)测试
import org.dbunit.DatabaseTestCase;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.junit.jupiter.api.Test;
public class ServletTest extends DatabaseTestCase {
private YourServletClass servlet;
@Override
protected IDataSet getDataSet() throws Exception {
return new XlsDataSet(new FileInputStream("src/test/resources/initialData.xls"));
}
@Override
protected DatabaseOperation getSetUpOperation() {
return DatabaseOperation.CLEAN_INSERT;
}
@Override
protected DatabaseOperation getTearDownOperation() {
return DatabaseOperation.NONE;
}
@Test
public void testServletUpdateMethod() throws Exception {
servlet = new YourServletClass(); // 假设的Servlet类
servlet.doPost(null, null); // 假设使用doPost方法来触发数据库更新
assertExpectedResults();
}
private void assertExpectedResults() throws Exception {
IDataSet expectedDataSet = new XlsDataSet(new FileInputStream("src/test/resources/expectedData.xls"));
DatabaseAssertion.assertEquals(expectedDataSet, getConnection().createDataSet());
}
}
3. 运行测试
在开发环境中运行这些测试,确保数据库连接配置正确,并且initialData.xls和expectedData.xls文件正确设置了测试数据。
以上就是使用JUnit 5、CdiUnit和DBUnit进行数据库相关测试的一个基本示例。这将确保测试不会对您的实际数据库产生永久影响。
二. 进一步的示例代码改善
为了更好的理解上面的方案我们把示例改善为,每种测试对象类的测试代码编写时提供两个测试用例,并确保不会出现锁表的问题。
首先,我们需要确保在测试结束时能够正确地回滚事务,避免数据的永久更改。其次,我们将为每个测试类添加一个额外的测试用例。
A. CDI 类(Service层)测试
import org.dbunit.DatabaseTestCase;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import com.oneandone.cdiunit.CdiRunner;
import javax.inject.Inject;
import javax.transaction.UserTransaction;
@ExtendWith(CdiRunner.class)
public class ServiceTest extends DatabaseTestCase {
@Inject
private YourServiceClass service;
@Inject
private UserTransaction transaction;
@Override
protected IDataSet getDataSet() throws Exception {
return new XlsDataSet(new FileInputStream("src/test/resources/initialData.xls"));
}
@Override
protected DatabaseOperation getSetUpOperation() {
return DatabaseOperation.CLEAN_INSERT;
}
@Override
protected DatabaseOperation getTearDownOperation() {
return DatabaseOperation.NONE;
}
@Test
public void testUpdateMethod() throws Exception {
transaction.begin();
try {
service.updateDataMethod(); // 假设这是更新数据的方法
assertExpectedResults("expectedData.xls");
} finally {
transaction.rollback();
}
}
@Test
public void testAnotherUpdateMethod() throws Exception {
transaction.begin();
try {
service.anotherUpdateMethod(); // 另一个更新数据的方法
assertExpectedResults("expectedData2.xls");
} finally {
transaction.rollback();
}
}
private void assertExpectedResults(String expectedDataFile) throws Exception {
IDataSet expectedDataSet = new XlsDataSet(new FileInputStream("src/test/resources/" + expectedDataFile));
DatabaseAssertion.assertEquals(expectedDataSet, getConnection().createDataSet());
}
}
B. 非CDI 类(Servlet层)测试
import org.dbunit.DatabaseTestCase;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.excel.XlsDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.junit.jupiter.api.Test;
public class ServletTest extends DatabaseTestCase {
private YourServletClass servlet;
@Override
protected IDataSet getDataSet() throws Exception {
return new XlsDataSet(new FileInputStream("src/test/resources/initialData.xls"));
}
@Override
protected DatabaseOperation getSetUpOperation() {
return DatabaseOperation.CLEAN_INSERT;
}
@Override
protected DatabaseOperation getTearDownOperation() {
return DatabaseOperation.NONE;
}
@Test
public void testServletUpdateMethod() throws Exception {
servlet = new YourServletClass(); // 假设的Servlet类
servlet.doPost(null, null); // 假设使用doPost方法来触发数据库更新
assertExpectedResults("expectedData.xls");
}
@Test
public void testAnotherServletMethod() throws Exception {
servlet = new YourServletClass();
servlet.doGet(null, null); // 假设使用doGet方法来触发数据库更新
assertExpectedResults("expectedData2.xls");
}
private void assertExpectedResults(String expectedDataFile) throws Exception {
IDataSet expectedDataSet = new XlsDataSet(new FileInputStream("src/test/resources/" + expectedDataFile));
DatabaseAssertion.assertEquals(expectedDataSet, getConnection().createDataSet());
}
}
注意事项
事务管理:确保每个测试方法都在事务中运行,并且在方法结束时回滚事务,这样可以避免测试对数据库造成永久更改。
锁表问题:通过使用事务并在测试结束时回滚,通常可以避免锁表问题。确保在测试数据库中没有其他并发操作可能导致锁表。
资源清理:在getTearDownOperation()方法中使用DatabaseOperation.NONE来确保不会在测试后清理数据库,因为所有操作都已经回滚。
以上改善后的代码示例展示了如何为每个测试类添加额外的测试用例,并且通过正确的事务管理来确保测试的隔离性和数据库的一致性。