H2の組み込みモードで単体テスト
単体テストとか、結合テストとかでテストスイート起動時にデータベースを起動して、終了時にデータベースをシャットダウンする例です。
@RunWith(Suite.class) @SuiteClasses( { ProductDaoImplTest.class, ProductServiceImplTest.class }) public class AllTests { private static org.h2.tools.Server server; @BeforeClass public static void beforeClass() throws Exception { Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { shutdownServer(); } })); server = org.h2.tools.Server.createTcpServer( new String[] { "-baseDir", "db\\h2", "-tcpPort", "9092" }).start(); } @AfterClass public static void afterClass() { shutdownServer(); } private static void shutdownServer() { if (server != null) { server.shutdown(); server = null; } } }
H2をテストスイートから組み込みモードで立ち上げています。AfterClassでH2のシャットダウンを行っていますが、JUnitがエラーで終了した時のために、addShutdownHook()にて VM 終了時にもシャットダウン処理をしています。
昔は単体テストがDBに依存するのをきらって、モックをしこしこと作成していたものですが、H2であればライブラリの様に扱うことができ、ORMを使えばデータベースの方言も吸収してくれるのでとっても楽です。
H2をインメモリで使用して単体テスト
H2 は JDBC の接続 URL の指定で簡単にインメモリデータベースとして使えます。Springだと、アプリケーションコンテキストで以下の様な指定とします。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.h2.Driver"/> <property name="url" value="jdbc:h2:mem:test"/> <property name="username" value="sa"/> <property name="password" value=""/> </bean>
jdbc:h2:mem: でコネクションが作成されると勝手にインメモリでデータベースが動きます。Hibernate
で、「hibernate.hbm2ddl.auto = create」とかしておくと、テストケースの実行がとても手軽になります。
H2による単体テストのサンプル
前述の動きの確認用のサンプルを書いておきます。Spring2.5 Hibernate3 JUnit4 H2 の組み合わせです。
エンティティとなるProductクラス
package etc9.domain.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Product { private Long id; private String name; private String category; public Product(){} public Product(String name, String category) { this.name = name; this.category = category; } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getCategory() { return category; } public void setCategory(String category) { this.category = category; } }
Product を操作するDao
package etc9.dao; import java.util.List; import etc9.domain.entity.Product; public interface ProductDao { List<Product> findByCategory(final String category); Product get(Long id); Product save(Product product); } package etc9.dao; import java.sql.SQLException; import java.util.List; import org.hibernate.Criteria; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.criterion.Restrictions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateTemplate; import etc9.domain.entity.Product; public class ProductDaoImpl implements ProductDao { private HibernateTemplate hibernateTemplate; @Autowired public void setSessionFactory(SessionFactory sessionFactory) { this.hibernateTemplate = new HibernateTemplate(sessionFactory); } @Override @SuppressWarnings("unchecked") public List<Product> findByCategory(final String category) { return (List<Product>) this.hibernateTemplate.execute(new HibernateCallback(){ @Override public Object doInHibernate(Session session) throws HibernateException, SQLException { Criteria criteria = session.createCriteria(Product.class); criteria.add(Restrictions.eq("category", category)); return criteria.list(); }}); } @Override public Product get(final Long id) { return (Product) this.hibernateTemplate.get(Product.class, id); } @Override public Product save(final Product product) { this.hibernateTemplate.save(product); return product; } }
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd"> <context:annotation-config/> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.h2.Driver"/> <property name="url" value="jdbc:h2:mem:test"/> <property name="username" value="sa"/> <property name="password" value=""/> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="hibernateProperties"> <value> hibernate.dialect = org.hibernate.dialect.H2Dialect hibernate.show_sql = true hibernate.format_sql = false hibernate.hbm2ddl.auto = create </value> </property> <property name="annotatedClasses"> <list> <value>etc9.domain.entity.Product</value> </list> </property> </bean> <bean id="productDao" class="etc9.dao.ProductDaoImpl"></bean> </beans>
組み込みモードで使う場合は以下の要領
<property name="url" value="jdbc:h2:tcp://localhost:9092/test"/>
動作確認用のテストケース
package etc9.dao; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import etc9.domain.entity.Product; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "/applicationContext.xml") public class ProductDaoImplTest { @Autowired private ProductDao dao; @Before public final void before() { Product product1 = new Product("name1", "category"); dao.save(product1); Product product2 = new Product("name2", "category"); dao.save(product2); } @Test public final void testFindByCategory() { for(Product p : dao.findByCategory("category")) { System.out.println(ReflectionToStringBuilder.toString(p)); } } }
テストスイートは先の通り