我正在寻找一种将DBUnit与Seam集成的良好方式,以便我可以轻松地为功能测试准备数据集。这是我想到的,一个与Seam集成并在每个测试方法前后添加DBUnit操作的测试超类。
关于org.jboss.seam.mock.SeamTest类是您通常在Seam应用程序中进行功能测试的基础类。它允许您在表示层级别轻松地编写交互脚本(对于整个会话或单个事件)。我扩展了这个类以添加一些DBUnit特定功能
/**
* Uses a JNDI datasource and JTA for DBUnit operations.
*
* @author christian.bauer@jboss.com
*/
public abstract class SeamDBUnitTest extends SeamTest {
private static Log log = LogFactory.getLog(SeamDBUnitTest.class);
private Context ctx;
private ReplacementDataSet dataSet;
@Configuration(beforeTestClass = true)
public void prepare() throws Exception {
log.debug("Preparing DBUnit setup for test class");
// Prepare JNDI context
ctx = new InitialContext();
// Load the base dataset file
URL input = Thread.currentThread()
.getContextClassLoader().getResource(getDataSetLocation());
if (input == null)
throw new RuntimeException(
"Dataset '" + getDataSetLocation() + "' not found in classpath"
);
dataSet = new ReplacementDataSet(
new FlatXmlDataSet(input.openStream())
);
dataSet.addReplacementObject("[NULL]", null);
}
@Configuration(beforeTestMethod = true)
public void beforeTestMethod() throws Exception {
executeOperations(getBeforeTestMethodStack());
}
@Configuration(afterTestMethod = true)
public void afterTestMethod() throws Exception {
executeOperations(getAfterTestMethodStack());
}
private void executeOperations(DatabaseOperation[] operations) throws Exception {
log.debug("Executing database operations in JTA transaction");
UserTransaction tx = getUserTransaction();
tx.begin();
IDatabaseConnection con = new DatabaseConnection( getConnection() );
// TODO: Remove this once DBUnit works with HSQL DB
DatabaseConfig config = con.getConfig();
config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new FixDBUnit());
for (DatabaseOperation op : operations) {
op.execute(con, dataSet);
}
tx.commit();
}
// Subclasses can/have to override the following methods
/**
* Return a JDBC <tt>Connection</tt> with disabled foreign key checking.
*/
protected Connection getConnection() throws Exception {
log.debug("Getting database connection through JNDI lookup of DataSource");
// Lookup Datasource in JNDI
DataSource ds = (DataSource)ctx.lookup(getDataSourceJNDIName());
Connection con = ds.getConnection();
// Disable foreign key constraint checking
// This really depends on the DBMS product... here for HSQL DB
con.prepareStatement("set referential_integrity FALSE")
.execute();
return con;
}
/**
* Add <tt>DatabaseOperation</tt> that run before every test method.
*/
protected DatabaseOperation[] getBeforeTestMethodStack() {
return new DatabaseOperation[]{};
}
/**
* Add <tt>DatabaseOperation</tt> that run after every test method.
*/
protected DatabaseOperation[] getAfterTestMethodStack() {
return new DatabaseOperation[]{};
}
/**
* The relative location of the data set on the classpath.
*/
protected abstract String getDataSetLocation();
/**
* The JNDI name of the datasource used by DBUnit.
*/
protected abstract String getDataSourceJNDIName();
}
这很简单:对于测试套件中的每个测试类,我从类路径中加载配置的DBUnit数据集,并将所有NULL标记替换为真实的null(请参阅DBUnit文档)。在每个测试方法之前(在Seam功能测试中具有会话范围),我运行一系列DBUnit操作,并在每个测试方法之后再次运行以进行清理。因此,我的功能测试中的每个会话都使用一个定义好的数据集。具体测试子类必须提供一些设置,例如DBUnit数据集文件的存储位置和使用的数据库连接名称。子类还可以覆盖getConnection()方法,如果需要自定义程序。(同时注意有关DBUnit/HSQL问题的TODO,也许有一天他们会真正修复这个问题。)
一个DBUnit数据集可能看起来像这样
<?xml version="1.0"?>
<dataset>
<USERS USER_ID ="1"
OBJ_VERSION ="0"
FIRSTNAME ="Root"
LASTNAME ="Toor"
USERNAME ="root"
PASSWORD ="secret"
EMAIL ="root@toor.tld"
RANK ="0"
IS_ADMIN ="true"
CREATED ="2006-06-26 13:45:00"
HOME_STREET ="[NULL]"
HOME_ZIPCODE ="[NULL]"
HOME_CITY ="[NULL]"
DEFAULT_BILLINGDETAILS_ID ="[NULL]"
/>
</dataset>
最后,这是一个具体的功能测试类
public class LoginLogoutFunction extends SeamDBUnitTest {
protected String getDataSetLocation() {
return "org/hibernate/ce/modules/user/test/basedata.xml";
}
protected String getDataSourceJNDIName() {
return "[=>java:/caveatemptorDatasource]";
}
protected DatabaseOperation[] getBeforeTestMethodStack() {
return new DatabaseOperation[] {
DatabaseOperation.CLEAN_INSERT
};
}
@Test
public void testLoginLogout() throws Exception {
// Test logged out state
new Script() {
// Test a component that requires a logged in user
@Override
protected void invokeApplication() {
assert !isSessionInvalid();
ChangePassword changePw =
(ChangePassword) Component.getInstance("changePassword", true);
String outcome = changePw.doChange();
assert "login".equals(outcome);
}
// Test if there is no loggedIn flag in the Session context
@Override
protected void renderResponse() {
assert !Manager.instance().isLongRunningConversation();
assert Contexts.getSessionContext().get("loggedIn") == null;
}
}.run();
...
前三个方法实现了并覆盖了DBUnit操作的设置,您可以为每个测试方法创建一个执行前后的DBUnit操作堆栈(本例中只有一个,在执行前运行)。测试方法testLoginLogout()这是一个单独的会话,我在其中运行了几个脚本(每个事件一个脚本),以测试我应用程序的登录和注销功能。如你所见,我的脚本中的方法是模拟JSF表示层事件。要查看不带DBUnit添加的全类,请浏览Seam教程,这来自酒店预订应用程序。
因此,一个测试类使用一个数据集,如果考虑到TestNG能够处理命名测试组和通配符匹配的能力,我认为这并不太限制。