当我们做DAO层测试时,一般都是做单元测试,而不是像集成测试那样把项目运行起来。本章就是基于Postgres来讲讲如何做DAO层的单元测试。
一、DBUtility
单元测试因为不能运行项目,所以我们需要自己实现一个嵌入式的数据库的运行和关闭。DBUtility类就是做这个工作的。
import com.opentable.db.postgres.embedded.EmbeddedPostgres;
import java.io.IOException;
public class DBUtility {
private static EmbeddedPostgres embeddedPostgres;
private static final int DB_PORT = 5435;
private DBUtility() {};
public synchronized static EmbeddedPostgres startDatabase(){
try {
if(embeddedPostgres == null) {
embeddedPostgres = EmbeddedPostgres.builder()
.setPort(DB_PORT).start();
}
} catch (IOException e) {
throw new RuntimeException("Failed initializing embedded postgres ");
}
return embeddedPostgres;
}
public synchronized static void shutdownDatabase() throws IOException {
if(embeddedPostgres != null){
embeddedPostgres.close();
}
}
public synchronized boolean isDatabaseStarted(){
return embeddedPostgres != null;
}
}
二、JpaPersistConfig
然后,我们需要配置DataSource和JpaTransactionManager等。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.Properties;
@Configuration
@EnableJpaRepositories(basePackages = "xxx.xxx.xxx.xxx.repository")
@PropertySource("classpath:jpa-persistent.properties")
@EnableTransactionManagement
public class JpaPersistConfig {
@Autowired
private Environment env;
@Bean
public DataSource dataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.username"));
dataSource.setPassword(env.getProperty("jdbc.password"));
return dataSource;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "xxx.xxx.xxx.xxx.entity" });
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
em.setJpaProperties(additionalProperties());
return em;
}
@Bean
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
final Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.jdbc.lob.non_contextual_creation", env.getProperty("hibernate.jdbc.lob.non_contextual_creation"));
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", env.getProperty("hibernate.cache.use_second_level_cache"));
hibernateProperties.setProperty("hibernate.cache.use_query_cache", env.getProperty("hibernate.cache.use_query_cache"));
return hibernateProperties;
}
}
其中,@EnableJpaRepositories用来扫描和发现指定包及其子包中的Repository
定义;@EnableTransactionManagement 用来开启事务支持;@PropertySource 用来加载外部资源文件。
jpa-persistent.properties 如下所示:
jdbc.driverClassName=org.postgresql.Driver
jdbc.url=jdbc:postgresql://localhost:5435/postgres
jdbc.username=postgres
jdbc.password=postgres
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.jdbc.lob.non_contextual_creation=true
hibernate.show_sql=true
hibernate.cache.use_second_level_cache=false
hibernate.cache.use_query_cache=false
三、AbstractJPATest
AbstractJPATest是所有DAO层测试类的父类。
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { JpaPersistConfig.class }, loader = AnnotationConfigContextLoader.class)
@Transactional
@DirtiesContext
@Sql(scripts = {"classpath:schema.sql", "classpath:test-data.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
public abstract class AbstractJPATest {
@BeforeClass
public static void beforeClass() throws Exception{
DBUtility.startDatabase();
}
@AfterClass
public static void afterClass() throws Exception {
// Suite测试已经关闭了,这里注释掉
// DBUtility.shutdownDatabase();
}
}
其中,@BeforeClass和@AfterClass分别指定了测试运行时启动和关闭数据库。@Transactional表示使用事务,@DirtiesContext表明在整个测试结束后关闭该测试的Application context并清除缓存。而@Sql注解的executionPhase参数使用BEFORE_TEST_METHOD,表明在测试方法运行前执行,scripts指定执行的脚本,也就是创建数据表和初始数据。@ContextConfiguration
注解用来标注我们想要导入到这个测试类的某些bean,这里也就是JpaPersistConfig中配置好的DataSource等。
四、实际测试类
实际测试类只需继承AbstractJPATest,即可注入Repository,来进行单元测试了。
import com.scb.xxx.xxx.xxx.model.CustomerInfo;
import com.scb.xxx.xxx.repository.EBBSCustomerTempRepository;
import com.scb.xxx.xxx.repository.entity.EBBSCustomerTemp;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import java.time.LocalDateTime;
import java.util.Optional;
public class EBBSCustomerTempRepoTest extends AbstractJPATest {
@Autowired
private EBBSCustomerTempRepository repository;
@Test
public void testFindEBBSCustomerTemp() {
Optional<EBBSCustomerTemp> customerTemp = repository.findById(3L);
Assert.assertNotNull(customerTemp.get());
Assert.assertEquals("xxxxx", customerTemp.get().getRelationshipNo());
Assert.assertEquals(1, customerTemp.get().getTaskNo());
//repository.findAll().stream().forEach(System.out::println);
}
@Test
public void testSaveEBBSCustomerTemp() {
EBBSCustomerTemp customerTemp = new EBBSCustomerTemp();
customerTemp.setRelationshipNo("xxxxx");
customerTemp.setCreateTime(LocalDateTime.now());
customerTemp.setTaskNo(2);
CustomerInfo info = new CustomerInfo();
info.setArmCode("xxx");
info.setRelationshipType("xxx");
info.setSegmentCode("61");
info.setSubSegmentCode("xx");
customerTemp.setCustomerInfo(info);
EBBSCustomerTemp saved = repository.save(customerTemp);
Assert.assertNotNull(saved.getId());
Assert.assertNotNull(saved.getCustomerInfo());
Assert.assertEquals("xxxx", saved.getRelationshipNo());
Assert.assertEquals("xxx", saved.getCustomerInfo().getArmCode());
Assert.assertEquals(2, saved.getTaskNo());
}
@Test
public void testFindEBBSCustomerTempWithPageable() {
int pageNo = 0;
int pageSize = 2;
Pageable pageable = PageRequest.of(pageNo, pageSize);
Page<EBBSCustomerTemp> tempPage = repository.findByTaskNo(pageable, 1);
tempPage.get().forEach(System.out::println);
int totalPage = tempPage.getTotalPages();
long totalElement = tempPage.getTotalElements();
Assert.assertEquals(3, totalPage);
Assert.assertEquals(5, totalElement);
Assert.assertEquals("xxx", tempPage.getContent().get(0).getRelationshipNo());
Assert.assertEquals("xxx", tempPage.getContent().get(1).getRelationshipNo());
while (tempPage.hasNext()) {
Pageable nextPageable = tempPage.nextPageable();
Page<EBBSCustomerTemp> nextTempPage = repository.findByTaskNo(nextPageable, 1);
nextTempPage.get().forEach(System.out::println);
Assert.assertNotNull(nextTempPage.getContent());
Assert.assertEquals(false, nextTempPage.getContent().isEmpty());
tempPage = nextTempPage;
}
}
}
五、Suite测试
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import java.io.IOException;
@RunWith(Suite.class)
@Suite.SuiteClasses({
xxxTest.class,
xxxRepoTest.class,
})
public class DAOTestSuite {
@BeforeClass
public static void beforeClass(){
DBUtility.startDatabase();
}
@AfterClass
public static void afterClass() throws IOException {
DBUtility.shutdownDatabase();
}
}