當我們做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();
}
}