原文地址:http://www.informit.com/guides/content.aspx?g=java&seqNum=507
与Hibernate编写应用程序时,你的首要目标是制定正确的域模型,包括对象和关系,然后让Hibernate您所选择的数据库方言担心这些对象的持久性。测试你的对象,您将在运行您的应用程序的确切数据库方言,它始终是一个好主意,但是从一个单元测试的角度来看,你真的只关注Hibernate的对象和它们之间的关系。正因为如此,Hypersonic SQL数据库(HSQLDB)提供了一种简单的机制来启动数据库单元测试,来验证您的域模型是正确的。
Hypersonic可以在多种模式下运行,但本次讨论的最大的区别有以下两种模式:
作为一个独立的服务器运行时,就像任何其他的数据库,您可以使用:启动数据库,然后连接和断开它作为必要HSQLDB运行。但是,从单元测试的角度来看,HSQLDB可以运行在内存中的数据库。这意味着,当你创建一个JDBC连接,创建在非数据库连接。HSQLDB的运行模式,在这种模式下,由您提供的JDBC URL。他们有下列口味:
内存中的文件模式之间的区别是:在内存中的数据库是空的,但初始化数据文件模式。我已经在过去的战略是创建一个独立的数据库,允许Hibernate的创建表,并添加为我的数据,将数据保存到一个脚本,然后使用基于文件的URL指向的脚本。有关脚本的好处是,它是原始的SQL,所以你可以自由预先填充数据库中你要测试的任何数据。
例如,清单1显示了一个示例HSQLDB创建一个数据库,并预先填充产品,产品类别以及用户脚本。
CREATE SCHEMA PUBLIC AUTHORIZATION DBA CREATE MEMORY TABLE CUSTOMER(CUSTOMER_ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,CUSTOMER_FIRSTNAME VARCHAR(128),CUSTOMER_LASTNAME VARCHAR(128),CUSTOMER_EMAIL VARCHAR(128),CUSTOMER_PASSWORD VARCHAR(64)) CREATE MEMORY TABLE ADDRESS(ADDRESS_ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,ADDRESS_ADDRESS1 VARCHAR(255),ADDRESS_ADDRESS2 VARCHAR(255),ADDRESS_CITY VARCHAR(255),ADDRESS_STATE VARCHAR(255),ADDRESS_ZIP VARCHAR(255),CUSTOMER_ID BIGINT NOT NULL,CONSTRAINT FKE66327D46DF50C34 FOREIGN KEY(CUSTOMER_ID) REFERENCES CUSTOMER(CUSTOMER_ID)) CREATE MEMORY TABLE CATEGORY(CATEGORY_ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,CATEGORY_NAME VARCHAR(255)) CREATE MEMORY TABLE ORDER_ITEM(ORDER_ITEM_ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,ORDER_ITEM_QUANTITY INTEGER,ORDER_ID BIGINT NOT NULL,PRODUCT_ID BIGINT NOT NULL) CREATE MEMORY TABLE PRODUCT(PRODUCT_ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,PRODUCT_NAME VARCHAR(255),PRODUCT_PRICE DOUBLE,PRODUCT_DESCRIPTION VARCHAR(255), PRODUCT_INVENTORY INTEGER) CREATE MEMORY TABLE PRODUCT_CATEGORIES(PRODUCT_ID BIGINT NOT NULL,CATEGORY_ID BIGINT NOT NULL, PRIMARY KEY(PRODUCT_ID,CATEGORY_ID),CONSTRAINT FK5A93E78C121E7834 FOREIGN KEY(CATEGORY_ID) REFERENCES CATEGORY(CATEGORY_ID),CONSTRAINT FK5A93E78CF8BBB8E0 FOREIGN KEY(PRODUCT_ID) REFERENCES PRODUCT(PRODUCT_ID)) CREATE MEMORY TABLE ORDERS(ORDER_ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,ORDER_SUBTOTAL DOUBLE,ORDER_TAX DOUBLE,ORDER_TOTAL DOUBLE,CUSTOMER_ID BIGINT NOT NULL, ADDRESS_ID BIGINT NOT NULL,CONSTRAINT FK8B7256E5AE379EC0 FOREIGN KEY(ADDRESS_ID) REFERENCES ADDRESS(ADDRESS_ID),CONSTRAINT FK8B7256E56DF50C34 FOREIGN KEY(CUSTOMER_ID) REFERENCES CUSTOMER(CUSTOMER_ID)) ALTER TABLE ORDER_ITEM ADD CONSTRAINT FK4BBDE984F8BBB8E0 FOREIGN KEY(PRODUCT_ID) REFERENCES PRODUCT(PRODUCT_ID) ALTER TABLE ORDER_ITEM ADD CONSTRAINT FK4BBDE984715B5200 FOREIGN KEY(ORDER_ID) REFERENCES ORDERS(ORDER_ID) ALTER TABLE CUSTOMER ALTER COLUMN CUSTOMER_ID RESTART WITH 16 ALTER TABLE ADDRESS ALTER COLUMN ADDRESS_ID RESTART WITH 1 ALTER TABLE CATEGORY ALTER COLUMN CATEGORY_ID RESTART WITH 6 ALTER TABLE ORDER_ITEM ALTER COLUMN ORDER_ITEM_ID RESTART WITH 1 ALTER TABLE PRODUCT ALTER COLUMN PRODUCT_ID RESTART WITH 7 ALTER TABLE ORDERS ALTER COLUMN ORDER_ID RESTART WITH 1 CREATE USER SA PASSWORD "" GRANT DBA TO SA SET WRITE_DELAY 10 SET SCHEMA PUBLIC INSERT INTO CUSTOMER VALUES(1,'Steven','Haines','[email protected]','steve') INSERT INTO CATEGORY VALUES(1,'Fruit') INSERT INTO CATEGORY VALUES(2,'Vegetables') INSERT INTO CATEGORY VALUES(3,'Meat') INSERT INTO CATEGORY VALUES(4,'Dairy') INSERT INTO CATEGORY VALUES(5,'Organic') INSERT INTO PRODUCT VALUES(1,'Apple',0.25E0,'Food',10) INSERT INTO PRODUCT VALUES(2,'Orange',0.5E0,'Food',10) INSERT INTO PRODUCT VALUES(3,'Banana',0.75E0,'Food',0) INSERT INTO PRODUCT VALUES(4,'Peas',1.5E0,'Food',10) INSERT INTO PRODUCT VALUES(5,'Carrots',1.0E0,'Food',10) INSERT INTO PRODUCT VALUES(6,'Organic Apple',0.75E0,'Food',10) INSERT INTO PRODUCT_CATEGORIES VALUES(1,1) INSERT INTO PRODUCT_CATEGORIES VALUES(2,1) INSERT INTO PRODUCT_CATEGORIES VALUES(3,1) INSERT INTO PRODUCT_CATEGORIES VALUES(4,2) INSERT INTO PRODUCT_CATEGORIES VALUES(5,2) INSERT INTO PRODUCT_CATEGORIES VALUES(6,1) INSERT INTO PRODUCT_CATEGORIES VALUES(6,5)
这个脚本可能不是你想手工编写的(至少不是表的创建),但你应该简单的INSERT语句填充数据表。
在本节的例子中,我将演示如何测试一个Spring Hibernate的DAO类。如果你不熟悉Spring和Hibernate的集成,使用Spring的HibernateDaoSupport类,请参阅我的文章Spring和Hibernate Java集成参考指南。总之,Spring提供了一个非常优雅与Hibernate集成,允许你创建一个数据源,该数据源注入到会话工厂bean,并扩展HibernateDaoSupport成一个类,然后注入该会话工厂。然后,从实现的角度看,您只需致电getHibernateTemplate()并执行类似的方法你可能会发现在Hibernate的Session类,如saveOrUpdate() 、find()或delete() 。清单2显示了我的applicationContext.xml的文件,我的测试案例的内容。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean id="productDao" class="com.naturalfoods.product.dao.HibernateProductDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="customerDao" class="com.naturalfoods.customer.dao.HibernateCustomerDaoImpl"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- HSQLDB Data Source --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="org.hsqldb.jdbcDriver" /> <property name="url" value="jdbc:hsqldb:mem:naturalfoods" /> <property name="username" value="sa" /> <property name="password" value="" /> <property name="initialSize" value="5" /> <property name="maxActive" value="10" /> <property name="poolPreparedStatements" value="true" /> <property name="maxOpenPreparedStatements" value="10" /> </bean> <!-- Hibernate Session Factory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="annotatedClasses"> <list> <value>com.naturalfoods.model.Address</value> <value>com.naturalfoods.model.Category</value> <value>com.naturalfoods.model.Customer</value> <value>com.naturalfoods.model.Order</value> <value>com.naturalfoods.model.OrderItem</value> <value>com.naturalfoods.model.Product</value> </list> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.connection.autocommit">true</prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean> </beans>
我的项目有其自己的一套Spring配置文件,但我只为我的测试类创建清单2。它创建了一个数据源,使用共享连接池(DBCP)注入到SessionFactory的数据源。SessionFactory有注解的类的列表,这代表了Hibernate的领域模型,以及一组属性。hibernate.hbm2ddl.auto属性设置为“ 更新 “让Hibernate来创建和更新表,需要匹配bean,它发现在其领域模式。transactionManager的bean这是指由一个HibernateTransactionManager,被SessionFactory的创建,这是我的两个DAO类,它扩展HibernateDaoSupport注入。
清单3显示了完整的源代码,为我CustomerDaoTest的类,它测试的CustomerDao实现。
package com.naturalfoods.customer.dao; import java.util.List; import junit.framework.Assert; import org.apache.log4j.Logger; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.naturalfoods.model.Customer; public class CustomerDaoTest { /** * Logger for debugging purposes */ private Logger logger = Logger.getLogger( CustomerDaoTest.class ); /** * A Spring application context that we'll create from a test application context and use to create * our DAO object (and data source, session factory, etc.) */ private static ApplicationContext ctx = null; /** * The CustomerDao that we'll be testing */ private CustomerDao dao; @BeforeClass public static void setUpBeforeClass() throws Exception { // Load the applicationContext.xml file ctx = new ClassPathXmlApplicationContext( "applicationContext.xml" ); } @Before public void setUp() { dao = ( CustomerDao )ctx.getBean( "customerDao" ); } @After public void tearDown() { dao = null; } /** * Tests to make sure that we can add */ @Test public void testAddCustomer() { // Create a customer Customer customer = new Customer(); customer.setFirstName( "Test-First" ); customer.setLastName( "Test-Last" ); customer.setEmail( "[email protected]" ); customer.setPassword( "t3st" ); // Add a customer to the database dao.addNewCustomer( customer ); // Load the customer into another object Customer customer2 = dao.loadCustomer( customer.getId() ); Assert.assertNotNull( "The customer that was created was unable to be loaded from the database", customer2 ); // Assert that the customer exists Assert.assertEquals( "First names do not match", "Test-First", customer2.getFirstName() ); Assert.assertEquals( "Last names do not match", "Test-Last", customer2.getLastName() ); Assert.assertEquals( "Email addresses do not match", "[email protected]", customer2.getEmail() ); Assert.assertEquals( "Passwords do not match", "t3st", customer2.getPassword() ); // Remove the customer from the database dao.removeCustomer( customer2 ); // Assert that the customer is no longer in the database Customer customer3 = dao.loadCustomer( customer.getId() ); Assert.assertNull( "The customer should have been deleted but it was not", customer3 ); System.out.println( "Customer3: " + customer3 ); } /** * Tests querying customers by their last name */ @Test public void testQueryByLastName() { // Create four customers Customer steve = new Customer( "Steven", "Haines", "[email protected]", "mypass" ); Customer linda = new Customer( "Linda", "Haines", "[email protected]", "mypass" ); Customer michael = new Customer( "Michael", "Haines", "[email protected]", "mypass" ); Customer someone = new Customer( "Someone", "Else", "[email protected]", "notmypass" ); // Add the four customers to the database dao.addNewCustomer( steve ); dao.addNewCustomer( linda ); dao.addNewCustomer( michael ); dao.addNewCustomer( someone ); // Query the database List<Customer> customers = dao.findCustomersByLastName( "Haines" ); // Assert that we found all of the records that we expected to find Assert.assertEquals( "Did not find the three customers we inserted into the database", 3, customers.size() ); // Debug if( logger.isDebugEnabled() ) { logger.debug( "All customers with a lastname of Haines:" ); for( Customer customer : customers ) { logger.debug( "Customer: " + customer ); } } // Clean up dao.removeCustomer( steve ); dao.removeCustomer( linda ); dao.removeCustomer( michael ); dao.removeCustomer( someone ); } }applicationContext.xml的文件在CLASSPATH中,加载和加载CustomerDao bean真的只归结为两行代码:
ctx = new ClassPathXmlApplicationContext( "applicationContext.xml" ); dao = ( CustomerDao )ctx.getBean( "customerDao" );
XmlBeanFactory中的类看起来是命名的资源,在这种情况下让applicationContext.xml加载其所有的bean类。它暴露了一个方法叫做的getBean() ,它作为一个参数的bean返回给调用者。无论您是否是使用Hibernate,你可以使用这两条线路由Spring管理加载任何资源。
这个例子中的美是测试用例的代码没有做任何事情比生产代码不同:,它加载bean类和执行他们的方法。使用Hibernate作为一个内存中的数据库的所有的工作是通过定义适当的JDBC URL。
通过数据源定义使用HSQLDB的内存数据库,你的测试用例,可以运行一个新的全新数据库每次执行的时候,并没有要求你保持自己启动该数据库。此外,如果你需要在你的数据库中的数据,你可以指定一个脚本文件HSQLDB启动时使用内存中的数据库可以使用已知的数据为你写你的测试案例预先填充。总之,HSQLDB让您轻松测试你的Hibernate DAO类以外的您的应用程序,写作时颗粒状的单元测试,这是理想的情况下。