使用Hibernate / JPA映射和JUnit测试DAO的公式

时间:2011-12-17 17:22:41

标签: java hibernate jpa junit tdd

我发现了许多与此相关的问题,但很多讨论都是围绕使用HSQL数据库而不是配置Eclipse和命令行来运行实际测试。

说我有以下的构成方案:

@Entity
@Table("Team")
public class Team {
    private Integer id;
    private String name;
    private String city;
    private String owner;
    private List<Player> players;

    @Column(name="TEAM_ID")
    public Integer getTeamId() {
       return this.id;
    }

    public void setTeamId(Integer id) {
       this.id = id;
    }

    @Column(name="TEAM_ID")
    public Integer getName() {
       return this.name;
    }

    public void setName(String name) {
       this.name = name;
    }

    // ... etc.
}

我们假设我们有以下数据访问层:

@Repository
public class TeamDao {

    @PersistenceContext(unitName="examplePU")
    EntityManger em;

    public Team saveOrUpdate(Team team) {
        this.em.merge(team);
    }

    // etc...
}

我希望能够编写类似于以下内容的集成测试:

public class TeamIntegrationTest {

    @Resource
    TeamDao teamDao;

    @Test
    public void resourceLoaded() {
        assertNotNull(teamDao);
    }

    @Test
    public void testInsertTeam() {
        Team team = new Team();
        team.setName("Packers");
        team.setCity("Green Bay");
        teamDao.saveOrUpdate(team);
    }
}

在提交以确保它正常工作之前,在我自己的开发环境中运行它需要什么?请提供可能有用的相关图书馆参考资料。

最后,我想创建一个可用于未来项目的公式(我也相信这是许多其他企业开发人员的目标)。


提议的公式

我觉得最好不要把这个作为一个解决方案输入并提名自己作为提供答案的人,我觉得最好是标记出能够满足我所寻求的并且扩展它的答案。配置是一种痛苦,这是我想通过提出这个问题来获取知识。最后,我选择依靠Spring来代表我们处理一堆烦人的事情。

首先,配置(假设src/{main,test,inttest}/javasrc/{main,test,inttest}/resources设置:

src/resources/java
|
| - META-INF
|    |
|    | - orm.xml
|    | - persistence.xml
| - applicationContext.xml
| - applicationContext-Integration.xml

orm.xml包含的内容不多,但是当它不存在时我遇到了问题:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
          http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" version="1.0">
</entity-mappings>

Persistence.xml包含JPA应了解的实体:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence 
        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="persistenceUnitName">
        <class>com.acme.project.className</class>
        <!-- Etc... -->
    </persistence-unit>
</persistence>

applicationContext.xml包含大部分配置:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Spring configuration for JPA/database/Transactions -->
<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"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="
            http://www.springframework.org/schema/beans
                http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/context
                http://www.springframework.org/schema/context/spring-context-3.0.xsd
            http://www.springframework.org/schema/aop
                http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
            http://www.springframework.org/schema/tx
                http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <context:annotation-config/>

    <context:component-scan base-package="com.acme.project.full.package.name" />

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager"
        p:entityManagerFactory-ref="entityManagerFactory" p:dataSource-ref="jndiDataSource"
        p:jpaDialect-ref="jpaDialect" />

    <bean id="jndiDataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.databaseType.jdbc.Driver" />
            <property name="url" value="jdbc:url://location:port/databaseName" />
            <property name="username" value="" />
            <property name="password" value="" />
    </bean>

    <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />

    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
        lazy-init="true" p:dataSource-ref="jndiDataSource"
        p:jpaVendorAdapter-ref="hibernateJpaVendorAdapter"
        p:persistenceUnitName="persistenceUnitName" p:jpaDialect-ref="jpaDialect">
    </bean>

    <bean id="hibernateJpaVendorAdapter"
        class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
        p:databasePlatform="org.hibernate.dialect.MySQLDialect" p:generateDdl="false"
        p:showSql="true" />

我们正在使用EntityManager,因此推荐AbstractTransactionalDataSourceSpringContextTests的解决方案对我们不起作用。相反,我们创建了以下内容,可以引入SpringJUnit4ClassRunner.class并正确设置applicationContext。

@ContextConfiguration(locations = { "classpath:applicationContext-JPA-Integration.xml" })
@RunWith(SpringJUnit4ClassRunner.class)
public abstract class SpringBasedIntegration { }

我们创建了这个泛型类,以便它可以在其他集成测试类中使用,

public class TeamIntegrationTest extends SpringBasedIntegration {

    @Resource
    TeamDao vitalDao;

    @Before() 
    public void before() {
        List<Team> teams = teamDao.getKnownValues("...");
        for(Team entry : teams) {
            vitalDao.deleteTeam(team);
        }
    }

    @Test
    public void testSomething() { 
        // vitalDao should be initialized with an entityManger!
    }

现在,这有一个没有滚动数据的弱点所以​​我引入了一个hack,它将使用@Before方法删除已知值,以便每个方法都有一个新的已知状态。这可能适用于某些人,但可能不适合所有人。

欢迎所有反馈。我希望社区能够为JPA / Hibernate集成测试提供一个记录良好的解决方案,我们都知道StackOverflow是Google上用于查询的最佳结果。

3 个答案:

答案 0 :(得分:4)

尝试使用Spring Framework,

您可以为您的JUNIT课程使用Spring Transactional Test;它运行测试,但操作完成后每个更改都会回滚。您可以将此行为更改为实际提交,这取决于您。

神奇是由一个名为 AbstractTransactionalDataSourceSpringContextTests 的类创建的,在您的情况下,您需要在本地项目中设置applicationcontext并使用eclipse junit runner运行测试。

这是一个使用AbstractTransactionalDataSourceSpringContextTests的好教程 http://zenoconsulting.wikidot.com/blog:8

其他链接 http://www.infoq.com/articles/testing-in-spring http://static.springsource.org/spring/docs/2.5.x/reference/testing.html#testcontext-support-classes-junit44

答案 1 :(得分:4)

卡洛斯提供的优质材料的一些补充。如果您想要符合Java EE并且不想将应用程序绑定到Spring,那么您只能使用HSQLDB + Maven。

Carlos提供的教程已经讨论了这些组合,但如果你不想,你就不需要Spring。您可以以编程方式创建PersistentContext,创建一个基本的JUnit测试用例,为您执行此操作:

@Ignore
public abstract class DaoBaseTestCase {

    private static final String TEST_UNIT_NAME = "test_unit";
    protected static EntityManagerFactory entityManagerFactory;
    protected static EntityManager entityManager;

    @BeforeClass
    public static void prepareEntityManagerFactory() {
        entityManagerFactory = Persistence.createEntityManagerFactory(TEST_UNIT_NAME);
        entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();
    }

    @After
    public abstract void rollbackTransactionAndReleaseEntityManager();

    @AfterClass
    public static void releaseEntityManager() {
        if (entityManager.getTransaction().isActive()) {
            entityManager.getTransaction().rollback();
        }

        if(entityManager.isOpen()) {
            entityManager.close();
        }
        entityManagerFactory.close();
    }
}

然后,您可以在DAO中为EntityManager创建getter / setter,以传递在基础DAO测试中创建的那个。

答案 2 :(得分:2)

我最近发布了一个名为TestFun-JEE的轻量级框架,用于使用JUnit测试EJB + JPA ++代码 - 该框架负责在不使用容器的情况下将EJB和Mockito模拟注入到测试代码中(也许我应该说框架正在嘲弄一个容器。)

@Local
public interface SomeDao {
    SomeEntity save(SomeEntity t);
    List<SomeEntity> getAll();
}

@Stateless
public class SomeDaoImpl implements SomeDao {

    @PersistenceContext(unitName = "TestFun")
    private EntityManager entityManager;

    @Override
    public SomeEntity save(SomeEntity t) {
        if (t.getId() == 0) {
            entityManager.persist(t);
        } else {
            entityManager.merge(t);
        }
        return t;
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<SomeEntity> getAll() {
        Query query = entityManager.createQuery("FROM SomeEntity AS be");
        return query.getResultList();
    }

}

@RunWith(EjbWithMockitoRunner.class)
public class DaoTest {

    @EJB
    private SomeDao dao;

    @Test
    public void saveOne() {
        dao.save(new SomeEntity(0, "1. one", "s"));
        List<SomeEntity> entities = dao.getAll();
        assertEquals(1, entities.size());
    }

    @Test
    public void getMany() {
        dao.save(new SomeEntity(0, "1. one", "s"));
        dao.save(new SomeEntity(0, "2. two", "r"));
        List<SomeEntity> entities = dao.getAll();
        assertEquals(2, entities.size());
        assertThat(entities).
                onProperty("name").
                isEqualTo(Arrays.asList("1. one", "2. two"));//Note, this using org.fest.assertions.Assertions.assertThat
    }

}