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

集成测试是企业发展中经常被忽视的领域。 这主要是由于为集成测试设置必要的基础架构的相关复杂性。 对于由数据库支持的应用程序,为集成测试设置数据库,并且一旦测试完成(例如,数据文件,模式等),就需要相当复杂和耗时,以确保测试的可重复性。 虽然已经有许多工具(例如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秒。 见下面的屏幕截图。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容