美文网首页
使用MongoDB和Spring数据进行集成测试

使用MongoDB和Spring数据进行集成测试

作者: 欧阳冉冉 | 来源:发表于2016-10-12 18:22 被阅读0次

    集成测试是企业发展中经常被忽视的领域。 这主要是由于为集成测试设置必要的基础架构的相关复杂性。 对于由数据库支持的应用程序,为集成测试设置数据库,并且一旦测试完成(例如,数据文件,模式等),就需要相当复杂和耗时,以确保测试的可重复性。 虽然已经有许多工具(例如DBUnit)和机制(例如测试后回滚)来辅助这一点,但是固有的复杂性和问题总是存在的。
    但是如果你使用MongoDB,有一个很酷和容易的方法来做你的单元测试,几乎简单的编写一个单元测试与嘲笑。 通过“EmbedMongo”,我们可以轻松地设置嵌入式MongoDB实例进行测试,一旦测试完成,内置的清理支持。 在本文中,我们将演示一个示例,其中EmbedMongo与JUnit一起用于集成测试Repository实现。

    上述设置的Maven POM看起来像这样。

    <?xml version="1.0" encoding="UTF-8"?>
    
    <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.yohanliyanage.blog.mongoit</groupId>
    
    <artifactId>mongo-it</artifactId>
    
    <version>1.0</version>
    
    <dependencies>
    
    <dependency>
    
    <groupId>org.springframework.data</groupId>
    
    <artifactId>spring-data-mongodb</artifactId>
    
    <version>1.0.3.RELEASE</version>
    
    <scope>compile</scope>
    
    </dependency>
    
    <dependency>
    
    <groupId>junit</groupId>
    
    <artifactId>junit</artifactId>
    
    <version>4.10</version>
    
    <scope>test</scope>
    
    </dependency>
    
    <dependency>
    
    <groupId>org.springframework</groupId>
    
    <artifactId>spring-context</artifactId>
    
    <version>3.1.3.RELEASE</version>
    
    <scope>compile</scope>
    
    </dependency>
    
    <dependency>
    
    <groupId>de.flapdoodle.embed</groupId>
    
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
    
    <version>1.26</version>
    
    <scope>test</scope>
    
    </dependency>
    
    </dependencies>
    
    </project>
    

    或者如果你喜欢Gradle(顺便说一下,Gradle是一个真棒构建工具,你应该检查,如果你还没有这样做)。

    apply plugin: 'java'
    
    apply plugin: 'eclipse'
    
    sourceCompatibility = 1.6
    
    group = "com.yohanliyanage.blog.mongoit"
    
    version = '1.0'
    
    ext.springVersion = '3.1.3.RELEASE'
    
    ext.junitVersion = '4.10'
    
    ext.springMongoVersion = '1.0.3.RELEASE'
    
    ext.embedMongoVersion = '1.26'
    
    repositories {
    
    mavenCentral()
    
    maven { url 'http://repo.springsource.org/release' }
    
    }
    
    dependencies {
    
    compile "org.springframework:spring-context:${springVersion}"
    
    compile "org.springframework.data:spring-data-mongodb:${springMongoVersion}"
    
    testCompile "junit:junit:${junitVersion}"
    
    testCompile "de.flapdoodle.embed:de.flapdoodle.embed.mongo:${embedMongoVersion}"
    
    }
    

    首先,这里是我们将存储在Mongo的文档。

    package com.yohanliyanage.blog.mongoit.model;
    
    import org.springframework.data.mongodb.core.index.Indexed;
    
    import org.springframework.data.mongodb.core.mapping.Document;
    
    /**
    
     * A Sample Document.
    
     * 
    
     * @author Yohan Liyanage
    
     * 
    
     */
    
    @Document
    
    public class Sample {
    
    @Indexed
    
    private String key;
    
    private String value;
    
    public Sample(String key, String value) {
    
    super();
    
    this.key = key;
    
    this.value = value;
    
    }
    
    public String getKey() {
    
    return key;
    
    }
    
    public void setKey(String key) {
    
    this.key = key;
    
    }
    
    public String getValue() {
    
    return value;
    
    }
    
    public void setValue(String value) {
    
    this.value = value;
    
    }
    
    }
    

    为了帮助存储和管理这个文档,让我们写一个简单的Repository实现。 存储库接口如下。

    package com.yohanliyanage.blog.mongoit.repository;
    
    import java.util.List;
    
    import com.yohanliyanage.blog.mongoit.model.Sample;
    
    /**
    
     * Sample Repository API.
    
     * 
    
     * @author Yohan Liyanage
    
     *
    
     */
    
    public interface SampleRepository {
    
    /**
    
     * Persists the given Sample.
    
     * @param sample
    
     */
    
    void save(Sample sample);
    
    /**
    
     * Returns the list of samples with given key.
    
     * @param sample
    
     * @return
    
     */
    
    List<Sample> findByKey(String key);
    
    }
    

    和实现...

    package com.yohanliyanage.blog.mongoit.repository;
    
    import java.util.List;
    
    import static org.springframework.data.mongodb.core.query.Query.query;
    
    import static org.springframework.data.mongodb.core.query.Criteria.*;
    
    import org.springframework.beans.factory.annotation.Autowired;
    
    import org.springframework.data.mongodb.core.MongoOperations;
    
    import org.springframework.stereotype.Repository;
    
    import com.yohanliyanage.blog.mongoit.model.Sample;
    
    /**
    
     * Sample Repository MongoDB Implementation.
    
     * 
    
     * @author Yohan Liyanage
    
     *
    
     */
    
    @Repository
    
    public class SampleRepositoryMongoImpl implements SampleRepository {
    
    @Autowired
    
    private MongoOperations mongoOps;
    
    /**
    
     * {@inheritDoc}
    
     */
    
    public void save(Sample sample) {
    
    mongoOps.save(sample);
    
    }
    
    /**
    
     * {@inheritDoc}
    
     */
    
    public List<Sample> findByKey(String key) {
    
    return mongoOps.find(query(where("key").is(key)), Sample.class);
    
    }
    
    /**
    
     * Sets the MongoOps implementation. 
    
     * 
    
     * @param mongoOps the mongoOps to set
    
     */
    
    public void setMongoOps(MongoOperations mongoOps) {
    
    this.mongoOps = mongoOps;
    
    }
    
    }
    

    为了连接这个,我们需要一个Spring Bean配置。 注意,我们不需要这个测试。 但为了完成,我已经包括了这一点。 XML配置如下。

    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans xmlns="http://www.springframework.org/schema/beans"
    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    
    xmlns:mongo="http://www.springframework.org/schema/data/mongo"
    
    xmlns:context="http://www.springframework.org/schema/context"
    
    xsi:schemaLocation="http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
    
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    
    <!-- Enable Annotation Driven Configuration -->
    
    <context:annotation-config />
    
    <!-- Component Scan Packages for Annotation Driven Configuration -->
    
    <context:component-scan base-package="com.yohanliyanage.blog.mongoit.repository" />
    
    <!-- Mongo DB -->
    
    <mongo:mongo host="127.0.0.1" port="27017" />
    
    <!-- Mongo DB Factory -->
    
    <mongo:db-factory dbname="mongoit" mongo-ref="mongo"/>
    
    <!-- Mongo Template -->
    
    <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    
    <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
    
    </bean>
    
    </beans>
    

    现在我们已经准备好使用Embed Mongo编写我们的存储库实现的集成测试。
    理想情况下,集成测试应该放在单独的源目录中,就像我们放置单元测试(例如src / test / java => src / integration-test / java)。
    然而,Maven和Gradle都是灵活的,所以你可以配置POM / build.gradle来处理这个。 然而,为了保持这个讨论简单和集中,我将把集成测试放在'src / test / java',但我不推荐这个实际应用程序。
    让我们开始编写集成测试。 首先,让我们从一个简单的基于JUnit的方法的Test开始。

    package com.yohanliyanage.blog.mongoit.repository;
    
    import static org.junit.Assert.fail;
    
    import org.junit.After;
    
    import org.junit.Before;
    
    import org.junit.Test;
    
    /**
    
     * Integration Test for {@link SampleRepositoryMongoImpl}.
    
     * 
    
     * @author Yohan Liyanage
    
     */
    
    public class SampleRepositoryMongoImplIntegrationTest {
    
    private SampleRepositoryMongoImpl repoImpl;
    
    @Before
    
    public void setUp() throws Exception {
    
    repoImpl = new SampleRepositoryMongoImpl();
    
    }
    
    @After
    
    public void tearDown() throws Exception {
    
    }
    
    @Test
    
    public void testSave() {
    
    fail("Not yet implemented");
    
    }
    
    @Test
    
    public void testFindByKey() {
    
    fail("Not yet implemented");
    
    }
    
    }
    

    当这个JUnit测试用例初始化时,我们需要激活EmbedMongo来启动一个嵌入式Mongo服务器。 此外,当测试用例结束时,我们需要清理DB。 以下代码段执行此操作。

    package com.yohanliyanage.blog.mongoit.repository;
    
    import static org.junit.Assert.fail;
    
    import java.io.IOException;
    
    import org.junit.*;
    
    import org.springframework.data.mongodb.core.MongoTemplate;
    
    import com.mongodb.Mongo;
    
    import com.yohanliyanage.blog.mongoit.model.Sample;
    
    import de.flapdoodle.embed.mongo.MongodExecutable;
    
    import de.flapdoodle.embed.mongo.MongodProcess;
    
    import de.flapdoodle.embed.mongo.MongodStarter;
    
    import de.flapdoodle.embed.mongo.config.MongodConfig;
    
    import de.flapdoodle.embed.mongo.config.RuntimeConfig;
    
    import de.flapdoodle.embed.mongo.distribution.Version;
    
    import de.flapdoodle.embed.process.extract.UserTempNaming;
    
    /**
    
     * Integration Test for {@link SampleRepositoryMongoImpl}.
    
     * 
    
     * @author Yohan Liyanage
    
     */
    
    public class SampleRepositoryMongoImplIntegrationTest {
    
    private static final String LOCALHOST = "127.0.0.1";
    
    private static final String DB_NAME = "itest";
    
    private static final int MONGO_TEST_PORT = 27028;
    
    private SampleRepositoryMongoImpl repoImpl;
    
    private static MongodProcess mongoProcess;
    
    private static Mongo mongo;
    
    private MongoTemplate template;
    
    @BeforeClass
    
    public static void initializeDB() throws IOException {
    
    RuntimeConfig config = new RuntimeConfig();
    
    config.setExecutableNaming(new UserTempNaming());
    
    MongodStarter starter = MongodStarter.getInstance(config);
    
    MongodExecutable mongoExecutable = starter.prepare(new MongodConfig(Version.V2_2_0, MONGO_TEST_PORT, false));
    
    mongoProcess = mongoExecutable.start();
    
    mongo = new Mongo(LOCALHOST, MONGO_TEST_PORT);
    
    mongo.getDB(DB_NAME);
    
    }
    
    @AfterClass
    
    public static void shutdownDB() throws InterruptedException {
    
    mongo.close();
    
    mongoProcess.stop();
    
    }
    
    @Before
    
    public void setUp() throws Exception {
    
    repoImpl = new SampleRepositoryMongoImpl();
    
    template = new MongoTemplate(mongo, DB_NAME);
    
    repoImpl.setMongoOps(template);
    
    }
    
    @After
    
    public void tearDown() throws Exception {
    
    template.dropCollection(Sample.class);
    
    }
    
    @Test
    
    public void testSave() {
    
    fail("Not yet implemented");
    
    }
    
    @Test
    
    public void testFindByKey() {
    
    fail("Not yet implemented");
    
    }
    
    }
    

    initializeDB()方法用@BeforeClass注释,以在测试用例之前启动它。 此方法触发绑定到给定端口的嵌入式MongoDB实例,并暴露设置为使用给定数据库的Mongo对象。 在内部,EmbedMongo在临时目录中创建必要的数据文件。
    当这个方法第一次执行时,EmbedMongo将下载必要的Mongo实现(如上面代码中的Version.V2_2_0所示),如果它不存在的话。 这是一个不错的设施,特别是当谈到连续集成服务器。 您不必在每个CI服务器中手动设置Mongo。 这是测试的一个外部依赖。
    在用@AfterClass注释的shutdownDB()方法中,我们停止了EmbedMongo进程。 这会在EmbedMongo中触发必要的清除操作,以删除临时数据文件,将状态恢复到执行测试用例之前的状态。
    我们现在更新了setUp()方法来构建Spring MongoTemplate对象,该对象由EmbedMongo公开的Mongo实例支持,并使用该模板设置我们的RepoImpl。 tearDown()方法被更新以删除“Sample”集合,以确保我们的每个测试方法都以clean状态开始。
    现在只是写实际测试方法的问题。
    让我们从保存方法测试开始。

    @Test
    
    public void testSave() {
    
    Sample sample = new Sample("TEST", "2");
    
    repoImpl.save(sample);
    
    int samplesInCollection = template.findAll(Sample.class).size();
    
    assertEquals("Only 1 Sample should exist collection, but there are " 
    
    + samplesInCollection, 1, samplesInCollection);
    
    }
    

    我们创建一个Sample对象,将它传递给repoImpl.save(),并断言以确保Sample集合中只有一个Sample。简单,直接的东西。
    下面是findByKey方法的测试方法。

    @Test
    
    public void testFindByKey() {
    
    // Setup Test Data
    
    List<Sample> samples = Arrays.asList(
    
    new Sample("TEST", "1"), new Sample("TEST", "25"),
    
    new Sample("TEST2", "66"), new Sample("TEST2", "99"));
    
    for (Sample sample : samples) {
    
    template.save(sample);
    
    }
    
    // Execute Test
    
    List<Sample> matches = repoImpl.findByKey("TEST");
    
    // Note: Since our test data (populateDummies) have only 2 
    
    // records with key "TEST", this should be 2
    
    assertEquals("Expected only two samples with key TEST, but there are " 
    
    + matches.size(), 2, matches.size());
    
    }
    

    最初,我们通过将一组Sample对象添加到数据存储中来设置数据。 重要的是我们在这里直接使用template.save(),因为repoImpl.save()是一个被测试的方法。 我们不是在这里测试,所以我们在数据设置过程中使用底层的“可信”template.save()。 这是单元/集成测试中的基本概念。 然后我们执行test'findByKey'下的方法,并断言以确保只有两个样本匹配我们的查询。
    同样,我们可以继续为每个存储库方法编写更多的测试,包括负测试。 这里是最终的集成测试文件。

    package com.yohanliyanage.blog.mongoit.repository;
    
    import static org.junit.Assert.*;
    
    import java.io.IOException;
    
    import java.util.Arrays;
    
    import java.util.List;
    
    import org.junit.*;
    
    import org.springframework.data.mongodb.core.MongoTemplate;
    
    import com.mongodb.Mongo;
    
    import com.yohanliyanage.blog.mongoit.model.Sample;
    
    import de.flapdoodle.embed.mongo.MongodExecutable;
    
    import de.flapdoodle.embed.mongo.MongodProcess;
    
    import de.flapdoodle.embed.mongo.MongodStarter;
    
    import de.flapdoodle.embed.mongo.config.MongodConfig;
    
    import de.flapdoodle.embed.mongo.config.RuntimeConfig;
    
    import de.flapdoodle.embed.mongo.distribution.Version;
    
    import de.flapdoodle.embed.process.extract.UserTempNaming;
    
    /**
    
     * Integration Test for {@link SampleRepositoryMongoImpl}.
    
     * 
    
     * @author Yohan Liyanage
    
     */
    
    public class SampleRepositoryMongoImplIntegrationTest {
    
    private static final String LOCALHOST = "127.0.0.1";
    
    private static final String DB_NAME = "itest";
    
    private static final int MONGO_TEST_PORT = 27028;
    
    private SampleRepositoryMongoImpl repoImpl;
    
    private static MongodProcess mongoProcess;
    
    private static Mongo mongo;
    
    private MongoTemplate template;
    
    @BeforeClass
    
    public static void initializeDB() throws IOException {
    
    RuntimeConfig config = new RuntimeConfig();
    
    config.setExecutableNaming(new UserTempNaming());
    
    MongodStarter starter = MongodStarter.getInstance(config);
    
    MongodExecutable mongoExecutable = starter.prepare(new MongodConfig(Version.V2_2_0, MONGO_TEST_PORT, false));
    
    mongoProcess = mongoExecutable.start();
    
    mongo = new Mongo(LOCALHOST, MONGO_TEST_PORT);
    
    mongo.getDB(DB_NAME);
    
    }
    
    @AfterClass
    
    public static void shutdownDB() throws InterruptedException {
    
    mongo.close();
    
    mongoProcess.stop();
    
    }
    
    @Before
    
    public void setUp() throws Exception {
    
    repoImpl = new SampleRepositoryMongoImpl();
    
    template = new MongoTemplate(mongo, DB_NAME);
    
    repoImpl.setMongoOps(template);
    
    }
    
    @After
    
    public void tearDown() throws Exception {
    
    template.dropCollection(Sample.class);
    
    }
    
    @Test
    
    public void testSave() {
    
    Sample sample = new Sample("TEST", "2");
    
    repoImpl.save(sample);
    
    int samplesInCollection = template.findAll(Sample.class).size();
    
    assertEquals("Only 1 Sample should exist in collection, but there are " 
    
    + samplesInCollection, 1, samplesInCollection);
    
    }
    
    @Test
    
    public void testFindByKey() {
    
    // Setup Test Data
    
    List<Sample> samples = Arrays.asList(
    
    new Sample("TEST", "1"), new Sample("TEST", "25"),
    
    new Sample("TEST2", "66"), new Sample("TEST2", "99"));
    
    for (Sample sample : samples) {
    
    template.save(sample);
    
    }
    
    // Execute Test
    
    List<Sample> matches = repoImpl.findByKey("TEST");
    
    // Note: Since our test data (populateDummies) have only 2 
    
    // records with key "TEST", this should be 2
    
    assertEquals("Expected only two samples with key TEST, but there are " 
    
    + matches.size(), 2, matches.size());
    
    }
    
    }
    

    另一方面,集成测试的一个关键问题是执行时间。 我们都希望保持我们的测试执行时间尽可能低,最好是几秒钟,以确保我们可以在CI期间运行所有测试,最小的构建和验证时间。 但是,由于集成测试依赖于底层基础结构,因此通常集成测试需要时间运行。 但是使用EmbedMongo,情况并非如此。 在我的机器中,上面的测试套件在1.8秒内运行,每种测试方法最多只有0.166秒。 见下面的屏幕截图。

    相关文章

      网友评论

          本文标题:使用MongoDB和Spring数据进行集成测试

          本文链接:https://www.haomeiwen.com/subject/ebbiyttx.html