用H2内存数据库来写DAO层的单元测试

在写DAO层的单元测试时,用mock的写法意义不大,因为基本上测不出什么东西,但如果采用真实的数据库又会有一些问题:

  1. 测试可能会干扰到真实的数据。
  2. 即使使用不同的库来解决上述问题,也要求测试环境预先配置好数据库,不方便CI/CD。

所以相比使用和生产环境相同的数据库,可以采用H2数据库来写单元测试。

H2数据库解决了上述两个痛点:

  1. 可以将数据存入内存里,测试跑完后就丢弃,对真实数据毫无影响。
  2. 可以集成进Java里,无需环境配置,引入依赖即可。

引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.14.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

这里搭配了Hibernate来一起使用。开发环境和线上环境使用MySQL,而测试环境使用H2

非测试环境的配置

属性配置

1
2
3
4
5
6
7
8
9
10
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
hbm2ddl:
auto: update
show_sql: false
jdbc:
driverClassName: com.mysql.jdbc.Driver
password: password
url: jdbc:mysql://localhost:3306/test
username: root

Java配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Configuration
@EnableTransactionManagement
public class HibernateConfig {
private final Environment environment;

@Autowired
public HibernateConfig(Environment environment) {
this.environment = environment;
}

@Bean
public LocalSessionFactoryBean sessionFactoryBean() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setHibernateProperties(hibernateProperties());
sessionFactory.setAnnotatedClasses(Apartment.class, DailySummary.class);

return sessionFactory;
}

@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));

return dataSource;
}

@Bean
@Autowired
public HibernateTransactionManager transactionManager(SessionFactory s) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(s);
return txManager;
}

private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.hbm2ddl.auto", environment.getRequiredProperty("hibernate.hbm2ddl.auto"));
return properties;
}
}

至此非测试环境的数据库配置就完成了,只需再加入一些实体类就可以通过自动装配sessionFactoryBean来使用数据库。

测试环境的配置

1
2
3
4
5
6
7
8
9
10
hibernate:
dialect: org.hibernate.dialect.H2Dialect
hbm2ddl:
auto: create
show_sql: true
jdbc:
driverClassName: org.h2.Driver
url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
username: root
password: password

H2提供了多种模式,其中URL中的mem代表的就是内存数据库。配置非常简单,只需重新提供一份配置文件并放在测试目录下就可以了。

假设非测试环境的配置文件路径是:\src\main\resources\application-db.yml,那么新的这一份路径就应该是:\src\test\resources\application-db.yml。Spring在运行测试时就会自动采用测试目录下的配置文件。

需要注意的是H2的配置其实并不需要usernamepassword,但是Spring在采用了测试目录下的配置文件后就不会再采用main目录下的同名文件,所以如果不提供这两项,前面的Java配置就会在运行时因为查找不到这两项而报错。

单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest
@Transactional
public class RentApartmentDaoImplTest {
@Autowired
@Qualifier("rentApartmentDaoImpl")
private ApartmentDao dao;
private Apartment apartment;

@Before
public void setUp() {
apartment = new Apartment("TEST", "test.com", 80, 20000, true, true);
}

@Test
public void shouldClearAllRecords() {
// given
dao.save(apartment);
dao.save(apartment);
dao.save(apartment);

// when
dao.clear();

// then
assertThat(dao.list().size(), is(0));
}
}
1
2
3
4
5
2018-10-30 18:49:32.072  INFO 18424 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Hibernate: drop table APARTMENT if exists
Hibernate: drop table DAILY_SUMMARY if exists
Hibernate: create table APARTMENT (id bigint generated by default as identity, AREA integer, DELETED boolean default false not null, FOR_RENT boolean default false not null, PUBLISHED_TODAY boolean, TITLE varchar(255), TOTAL_PRICE integer, UNIT_PRICE integer, URL varchar(255), primary key (id))
Hibernate: create table DAILY_SUMMARY (id bigint generated by default as identity, DATE varchar(255), DELETED boolean default false not null, FIRSTHAND_TURNOVER INT default -1 not null, SECONDHAND_TURNOVER INT default -1 not null, UNIT_RENTING_PRICE INT default -1 not null, UNIT_SELLING_PRICE INT default -1 not null, primary key (id))

可以看到,数据库已经切换到了H2。

用Demo验证ArrayList的线程不安全

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×