补习系列(17)-springboot mongodb 内嵌数据库【华为云技术分享】
目录
简介
前面的文章中,我们介绍了如何在SpringBoot 中使用MongoDB的一些常用技巧。
那么,与使用其他数据库如 MySQL 一样,我们应该怎么来做MongoDB的单元测试呢?
使用内嵌数据库的好处是不需要依赖于一个外部环境,如果每一次跑单元测试都需要依赖一个稳定的外部环境,那么这样的测试是极不稳定的。
为了更欢快的使用MongoDB,这里提供两种使用内嵌数据库做单元测试的方式。
一、使用 flapdoodle.embed.mongo
开源地址
该组件的大致原理是,在当前环境中自动下载MongoDB并拉起进程,测试后再做关闭。
先演示一遍如何使用:
A. 引入依赖
1 <dependency> 2 <groupId>de.flapdoodle.embed</groupId> 3 <artifactId>de.flapdoodle.embed.mongo</artifactId> 4 <version>1.50.5</version> 5 <scope>test</scope> 6 </dependency>
B. 准备测试类
编写一个基础类:
1 @RunWith(SpringRunner.class) 2 @SpringBootTest(classes = DemoBoot.class) 3 @ActiveProfiles("test") 4 public class BaseEmbededMongoTest { 5 6 private static final Logger logger = LoggerFactory.getLogger(BaseEmbededMongoTest.class); 7 protected static final MongodStarter starter = MongodStarter.getDefaultInstance(); 8 protected static MongodExecutable _mongodExe; 9 protected static MongodProcess _mongod; 10 11 // 确保与配置一致 12 protected static final String host = "127.0.0.1"; 13 protected static final int port = 27027; 14 15 @BeforeClass 16 public static void setUp() throws Exception { 17 _mongodExe = starter.prepare(new MongodConfigBuilder().version(Version.Main.PRODUCTION) 18 .net(new Net(host, port, Network.localhostIsIPv6())).build()); 19 _mongod = _mongodExe.start(); 20 21 logger.info("mongod started on {}:{}", host, port); 22 } 23 24 @AfterClass 25 public static void tearDown() throws Exception { 26 _mongod.stop(); 27 _mongodExe.stop(); 28 } 29 }
BaseEmbededMongoTest 实现了:
- 测试启动前启动MongoDB进程;
- 测试完成后关闭MongoDB进程;
让业务测试类继承于基础类:
1 public class BookServiceTest extends BaseEmbededMongoTest{ 2 3 @Autowired 4 private BookService bookService; 5 6 @Autowired 7 private BookRepository bookRepository; 8 ...
C. 完善配置
为了避免冲突,需要关闭EmbeddedMongoAutoConfiguration。
1 @SpringBootApplication 2 @EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class}) 3 public class BootSampleMongo { 4 ...
最后一步,为了让业务代码能连接到自启动的MongoDB,需要做对应的配置:
在src/test/resources目录中编辑 application-test.properties
1 spring.data.mongodb.host=localhost 2 spring.data.mongodb.port=27027 3 spring.data.mongodb.database=test
D. 启动测试
执行业务测试类,可以看到一系列输出:
1 //下载 2 Download PRODUCTION:Windows:B64 START 3 Download PRODUCTION:Windows:B64 DownloadSize: 147911698 4 Download PRODUCTION:Windows:B64 0% 1% 2% 3% 4% 5% 6% 7% 8% 9% 10% 11% 5 ... 6 //启动继承 7 [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] db version v3.2.1 8 [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] git version: a14d55980c2cdc565d4704a7e3ad37e4e535c1b2 9 [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] allocator: tcmalloc 10 [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] modules: none 11 [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] build environment: 12 [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] distmod: 2008plus 13 [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] distarch: x86_64 14 [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] target_arch: x86_64 15 ... 16 [mongod output] 2019-03-02T15:43:02.070+0800 I NETWORK [initandlisten] waiting for connections on port 27027 17 //单元测试 18 ... 19 //关闭进程 20 [mongod output] 2019-03-02T15:43:20.838+0800 I COMMAND [conn3] terminating, shutdown command received 21 [mongod output] 2019-03-02T15:43:20.838+0800 I FTDC [conn3] Shutting down full-time diagnostic data capture 22 [mongod output] 2019-03-02T15:43:20.846+0800 I CONTROL [conn3] now exiting 23 [mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] shutdown: going to close listening sockets... 24 [mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] closing listening socket: 456 25 [mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] shutdown: going to flush diaglog... 26 [mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] shutdown: going to close sockets... 27 [mongod output] 2019-03-02T15:43:20.911+0800 I NETWORK [conn1] end connection 127.0.0.1:52319 (2 connections now open) 28 [mongod output] 2019-03-02T15:43:20.911+0800 I STORAGE [conn3] WiredTigerKVEngine shutting down 29 [mongod output] 2019-03-02T15:43:20.916+0800 I NETWORK [conn2] end connection 127.0.0.1:52320 (1 connection now open) 30 [mongod output] 2019-03-02T15:43:20.943+0800 I STORAGE [conn3] shutdown: removing fs lock... 31 [mongod output] 2019-03-02T15:43:20.943+0800 I CONTROL [conn3] dbexit: rc: 0
注:首次使用该组件时需要下载安装包,过程比较缓慢需要些耐心..
细节
细心的同学可能注意到了,我们为什么要特别规避EmbeddedMongoAutoConfiguration这个类呢?
在SpringBoot 官方文档中提到了 EmbeddedMongoAutoConfiguration,其作用主要是:
- 自动检测 flapdoodle.embed.mongo组件是否被引入;
- 如果当前的运行环境中能找到组件,则会自动启动组件,并在程序退出时做销毁
我们简单看一下其实现:
1 @Configuration 2 @EnableConfigurationProperties({ MongoProperties.class, EmbeddedMongoProperties.class }) 3 @AutoConfigureBefore(MongoAutoConfiguration.class) 4 @ConditionalOnClass({ Mongo.class, MongodStarter.class }) 5 public class EmbeddedMongoAutoConfiguration { 6 private final MongoProperties properties; 7 private final EmbeddedMongoProperties embeddedProperties; 8 9 @Bean(initMethod = "start", destroyMethod = "stop") 10 @ConditionalOnMissingBean 11 public MongodExecutable embeddedMongoServer(IMongodConfig mongodConfig) 12 throws IOException { 13 Integer configuredPort = this.properties.getPort(); 14 if (configuredPort == null || configuredPort == 0) { 15 setEmbeddedPort(mongodConfig.net().getPort()); 16 } 17 MongodStarter mongodStarter = getMongodStarter(this.runtimeConfig); 18 return mongodStarter.prepare(mongodConfig); 19 }
不难猜到,该配置类已经完成了我们在单元测试中所需要的一切事情,那为什么还需要BaseEmbededMongoTest?
答案在于,我们可能会对MongoDB的连接池做许多定制,如下面的代码:
1 @Configuration 2 public void MongoConfig{ 3 @Bean 4 public MongoDbFactory mongoDbFactory(){ 5 ... 6 } 7 }
类似这样的定制,会让MongoAutoConfiguration失效。即SpringDataMongo 的初始化会先于Embeded实例的启动,导致失败。
通过自定义的实现则可以规避该问题,当然如果通过Profile设定也可以进行规避。
二、使用Fongo
开源地址
Fongo 是由 Fousquare 开发团队开源的一款真正的内存式MongoDB,非常适用于轻量级的单元测试。
这个名字.. 不错哈
Fongo 支持对Java-Driver的各种CRUD指令进行解析,并模拟数据在内存中的存储管理操作,可以认为其提供了一层JavaDriver的代理。
同时,该框架是线程安全的,所有的集合读写操作都能得到同步保护
接下来是如何使用:
A. 引入框架
1 <!-- fongo face mongo --> 2 <dependency> 3 <groupId>com.github.fakemongo</groupId> 4 <artifactId>fongo</artifactId> 5 <version>2.1.0</version> 6 <scope>test</scope> 7 <exclusions> 8 <exclusion> 9 <groupId>com.fasterxml.jackson.core</groupId> 10 <artifactId>jackson-core</artifactId> 11 </exclusion> 12 <exclusion> 13 <groupId>com.fasterxml.jackson.core</groupId> 14 <artifactId>jackson-databind</artifactId> 15 </exclusion> 16 </exclusions> 17 </dependency>
注:fongo依赖于jackson,可能与SpringBoot项目冲突,这里显示将其剔除。
B. 准备测试类
编写一个基于Fongo的类:
1 @ActiveProfiles("test") 2 @RunWith(SpringRunner.class) 3 @SpringBootTest(classes = BootSampleMongo.class) 4 @Import(TestConfig.class) 5 public class BaseFongoTest { 6 7 }
这里使用@Import导入了一个TestConfig,用于初始化Fongo实例,如下:
1 @TestConfiguration 2 @Profile("test") 3 public class TestConfig extends AbstractMongoConfiguration { 4 5 @Autowired 6 private Environment env; 7 8 @Override 9 protected String getDatabaseName() { 10 return env.getProperty("spring.data.mongodb.database", "test"); 11 } 12 13 @Override 14 public Mongo mongo() throws Exception { 15 return new Fongo(getDatabaseName()).getMongo(); 16 } 17 18 }
这样,通过继承于AbstractMongoConfiguration,可以省去配置MongoDbFactory之类的工作。
需要注意的是,如果业务代码做了一些连接池的定制,如MongoDbFactory/MongoTemplate的定义,则需要通过Profile进行隔离,避免在测试过程中出错:
1 @Configuration 2 @Profile("prod") 3 public class ProdMongoConfig { 4 ...
C.业务测试
准备好上面的工作后,则可以用到业务测试代码上:
1 public class BookServiceTest extends BaseFongoTest{ 2 3 @Autowired 4 private BookService bookService; 5 6 @Autowired 7 private BookRepository bookRepository;
至此,我们已经完成了Fongo 的使用。
参考文档
springboot-with-mongo-embed
flapdoodle-embed-mongo-github
another-embededmongo-fongo
小结
随着MongoDB 在Web开发中的应用越来越广,许多配套的框架及工具也在逐步完善。
本文介绍了两种在SpringBoot 框架上使用内嵌MongoDB的方式,从简易性来看,个人更推荐Fongo的方案。
由于Fongo 更接近于H2(一种内存SQL数据库)的实现,整个测试过程中不需要开启MongoDB进程,也免去了远程下载软件的烦恼。
所有的操作均在内存中完成,会令整个测试更加的高效,然而其仅有的缺点是无法支持一些原生的MongoDB管理命令(一般也不会用到)。
当然,读者也可以根据自己的需求自行选择。
欢迎继续关注"美码师的补习系列-springboot篇" ,期待更多精彩内容^-^
作者:美码师
HDC.Cloud 华为开发者大会2020 即将于2020年2月11日-12日在深圳举办,是一线开发者学习实践鲲鹏通用计算、昇腾AI计算、数据库、区块链、云原生、5G等ICT开放能力的最佳舞台。