• 补习系列(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开放能力的最佳舞台。

    欢迎报名参会

  • 相关阅读:
    [Wix] 搞了这么久才知道Wix怎么装
    [Wix] Wix Library Tool : lit.exe
    [Wix] Wix代码生成器:tallow
    [Wix] 不同的产品用了一同ID
    [Wix] 安装时建Services, 用户, 虚拟目录....
    [Wix] 添加自定义Action
    [Wix] 有IIS虚拟目录的程序安装(XP or Win2003)
    [wix]ExampleCPlusPlusCustomAction
    几个OpenSource的源代码管理软件
    Bug管理
  • 原文地址:https://www.cnblogs.com/huaweicloud/p/12017160.html
Copyright © 2020-2023  润新知