我正在寻找一种将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能够处理命名测试组和通配符匹配的能力,我认为这并不太限制。