使用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秒。 見下面的屏幕截圖。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容