使用 MongoDB 进行单元测试

发布于 2024-12-04 09:26:02 字数 522 浏览 1 评论 0原文

我选择的数据库是 MongoDB。我正在编写一个数据层 API 来从客户端应用程序中抽象实现细节 - 也就是说,我本质上提供了一个公共接口(充当 IDL 的对象)。

我正在以 TDD 方式测试我的逻辑。在每个单元测试之前,都会调用 @Before 方法来创建数据库单例,之后,当测试完成时,会调用 @After 方法来删​​除数据库。这有助于促进单元测试之间的独立性。

几乎所有单元测试,即执行上下文查询,都需要事先发生某种插入逻辑。我的公共接口提供了一个插入方法 - 然而,使用此方法作为每个单元测试的先驱逻辑似乎是不正确的。

我确实需要某种模拟机制,但是,我在模拟框架方面没有太多经验,而且 Google 似乎没有返回任何可能与 MongoDB 一起使用的模拟框架。

其他人在这些情况下会做什么?也就是说,人们如何对与数据库交互的代码进行单元测试?

另外,我的公共接口连接到外部配置文件中定义的数据库 - 使用此连接进行单元测试似乎不正确 - 再次,这种情况会受益于某种模拟?

My database of choice is MongoDB. I'm writing a data-layer API to abstract implementation details from client applications - that is, I'm essentially providing a single public interface (an object which acts as an IDL).

I'm testing my logic as I go in a TDD manner. Before each unit test, an @Before method is called to create a database singleton, after which, when the test completes, an @After method is called to drop the database. This helps to promote independence among unit tests.

Nearly all unit tests, i.e. performing a contextual query, require some kind of insertion logic to occur before hand. My public interface provides an insert method - yet, it seems incorrect to use this method as precursor logic to each unit test.

Really I need some kind of mocking mechanism, yet, I haven't had much experience with mocking frameworks, and it seems that Google returns nothing re a mocking framework one might use with MongoDB.

What do others do in these situations? That is, how do people unit test code that interacts with a database?

Also, my public interface connects to a database defined in a external configuration file - it seems incorrect to use this connection for my unit testing - again, a situation that would benefit from some kind of mocking?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

命硬 2024-12-11 09:26:02

从技术上讲,与数据库(nosql 或其他方式)对话的测试不是单元测试,因为测试是测试与外部系统的交互,而不仅仅是测试孤立的代码单元。然而,与数据库交互的测试通常非常有用,并且通常足够快,可以与其他单元测试一起运行。

通常我有一个服务接口(例如UserService),它封装了处理数据库的所有逻辑。依赖于 UserService 的代码可以使用 UserService 的模拟版本并且易于测试。

当测试与 Mongo 通信的服务的实现时(例如 MongoUserService),最简单的方法是编写一些 java 代码来启动/停止本地计算机上的 mongo 进程,并让您的 MongoUserService 连接到该进程,请参阅此 一些注释的问题

您可以在测试 MongoUserService 时尝试模拟数据库的功能,但通常这太容易出错,并且不能测试您真正想要测试的内容,即与真实数据库的交互。因此,在为 MongoUserService 编写测试时,您需要为每个测试设置一个数据库状态。查看DbUnit,了解使用数据库执行此操作的框架示例。

Technically tests that talk to a database (nosql or otherwise) are not unit tests, as the tests are testing interactions with an external system, and not just testing an isolated unit of code. However tests that talk to a database are often extremely useful, and are often fast enough to run with the other unit tests.

Usually I have a Service interface (eg UserService) which encapsulates all the logic for dealing with the database. Code that relies on UserService can use a mocked version of UserService and is easily tested.

When testing the implementation of the Service that talks to Mongo, (eg MongoUserService) it is easiest to write some java code that will start/stop a mongo process on the local machine, and have your MongoUserService connect to that, see this question for some notes.

You could try to mock the functionality of the database while testing MongoUserService, but generally that is too error prone, and doesn't test what you really want to test, which is interaction with a real database. So when writing tests for MongoUserService, you set up a database state for each test. Look at DbUnit for an example of a framework for doing so with a database.

相对绾红妆 2024-12-11 09:26:02

正如 sbridges 在这篇文章中所写,不拥有从逻辑中抽象数据访问的专用服务(有时也称为存储库或 DAO)是一个坏主意。然后您可以通过提供 DAO 的模拟来测试逻辑。

我所做的另一种方法是创建 Mongo 对象的 Mock(例如 PowerMockito),然后返回适当的结果。
这是因为您不必在单元测试中测试数据库是否工作,但更重要的是您应该测试是否将正确的查询发送到数据库。

Mongo mongo = PowerMockito.mock(Mongo.class);
DB db = PowerMockito.mock(DB.class);
DBCollection dbCollection = PowerMockito.mock(DBCollection.class);

PowerMockito.when(mongo.getDB("foo")).thenReturn(db);
PowerMockito.when(db.getCollection("bar")).thenReturn(dbCollection);

MyService svc = new MyService(mongo); // Use some kind of dependency injection
svc.getObjectById(1);

PowerMockito.verify(dbCollection).findOne(new BasicDBObject("_id", 1));

这也是一个选择。当然,模拟的创建和适当对象的返回只是作为上面的示例进行编码。

As sbridges wrote in this post it is a bad idea not to have a dedicated service (sometimes also known as repository or DAO) which abstracts the data access from the logic. Then you could test the logic by providing a mock of the DAO.

Another approach which I do is to create a Mock of the Mongo object (e.g. PowerMockito) and then return the appropriate results.
This because you don't have to test if the database works in unit tests but more over you should test if the right query was sent to the databse.

Mongo mongo = PowerMockito.mock(Mongo.class);
DB db = PowerMockito.mock(DB.class);
DBCollection dbCollection = PowerMockito.mock(DBCollection.class);

PowerMockito.when(mongo.getDB("foo")).thenReturn(db);
PowerMockito.when(db.getCollection("bar")).thenReturn(dbCollection);

MyService svc = new MyService(mongo); // Use some kind of dependency injection
svc.getObjectById(1);

PowerMockito.verify(dbCollection).findOne(new BasicDBObject("_id", 1));

That would also be an option. Of course the creation of the mocks and returning of the appropriate objects is just coded as an example above.

一身仙ぐ女味 2024-12-11 09:26:02

我用 Java 编写了一个 MongoDB 假实现: mongo-java-server

默认是一个 in-内存后端,可以在单元和集成测试中轻松使用。

例子

MongoServer server = new MongoServer(new MemoryBackend());
// bind on a random local port
InetSocketAddress serverAddress = server.bind();

MongoClient client = new MongoClient(new ServerAddress(serverAddress));

DBCollection coll = client.getDB("testdb").getCollection("testcoll");
// creates the database and collection in memory and inserts the object
coll.insert(new BasicDBObject("key", "value"));

assertEquals(1, collection.count());
assertEquals("value", collection.findOne().get("key"));

client.close();
server.shutdownNow();

I wrote a MongoDB fake implementation in Java: mongo-java-server

Default is a in-memory backend, that can be easily used in Unit and Integration tests.

Example

MongoServer server = new MongoServer(new MemoryBackend());
// bind on a random local port
InetSocketAddress serverAddress = server.bind();

MongoClient client = new MongoClient(new ServerAddress(serverAddress));

DBCollection coll = client.getDB("testdb").getCollection("testcoll");
// creates the database and collection in memory and inserts the object
coll.insert(new BasicDBObject("key", "value"));

assertEquals(1, collection.count());
assertEquals("value", collection.findOne().get("key"));

client.close();
server.shutdownNow();
清君侧 2024-12-11 09:26:02

今天我认为最好的做法是使用 testcontainers 库 (Java) 或 testcontainers-python 端口。它允许使用 Docker 镜像进行单元测试。
要在 Java 代码中运行容器,只需实例化 GenericContainer 对象 (示例):

GenericContainer mongo = new GenericContainer("mongo:latest")
    .withExposedPorts(27017);

MongoClient mongoClient = new MongoClient(mongo.getContainerIpAddress(), mongo.getMappedPort(27017));
MongoDatabase database = mongoClient.getDatabase("test");
MongoCollection<Document> collection = database.getCollection("testCollection");

Document doc = new Document("name", "foo")
        .append("value", 1);
collection.insertOne(doc);

Document doc2 = collection.find(new Document("name", "foo")).first();
assertEquals("A record can be inserted into and retrieved from MongoDB", 1, doc2.get("value"));

或在 Python 上(示例):

mongo = GenericContainer('mongo:latest')
mongo.with_bind_ports(27017, 27017)

with mongo_container:
    def connect():
        return MongoClient("mongodb://{}:{}".format(mongo.get_container_host_ip(),
                                                    mongo.get_exposed_port(27017)))

    db = wait_for(connect).primer
    result = db.restaurants.insert_one(
        # JSON as dict object
    )

    cursor = db.restaurants.find({"field": "value"})
    for document in cursor:
        print(document)

Today I think the best practice is to use testcontainers library (Java) or testcontainers-python port on Python. It allows to use Docker images with unit tests.
To run container in Java code just instantiate GenericContainer object (example):

GenericContainer mongo = new GenericContainer("mongo:latest")
    .withExposedPorts(27017);

MongoClient mongoClient = new MongoClient(mongo.getContainerIpAddress(), mongo.getMappedPort(27017));
MongoDatabase database = mongoClient.getDatabase("test");
MongoCollection<Document> collection = database.getCollection("testCollection");

Document doc = new Document("name", "foo")
        .append("value", 1);
collection.insertOne(doc);

Document doc2 = collection.find(new Document("name", "foo")).first();
assertEquals("A record can be inserted into and retrieved from MongoDB", 1, doc2.get("value"));

or on Python (example):

mongo = GenericContainer('mongo:latest')
mongo.with_bind_ports(27017, 27017)

with mongo_container:
    def connect():
        return MongoClient("mongodb://{}:{}".format(mongo.get_container_host_ip(),
                                                    mongo.get_exposed_port(27017)))

    db = wait_for(connect).primer
    result = db.restaurants.insert_one(
        # JSON as dict object
    )

    cursor = db.restaurants.find({"field": "value"})
    for document in cursor:
        print(document)
夏末的微笑 2024-12-11 09:26:02

我很惊讶到目前为止没有人建议使用 fakemongo 。它很好地模拟了 mongo 客户端,并且都在同一个 JVM 上运行并进行了测试 - 因此集成测试变得健壮,并且在技术上更接近真正的“单元测试”,因为没有发生外部系统交互。这就像使用嵌入式 H2 对 SQL 代码进行单元测试。
我很高兴在单元测试中使用 fakemongo 以端到端的方式测试数据库集成代码。在测试 spring 上下文中考虑此配置:

@Configuration
@Slf4j
public class FongoConfig extends AbstractMongoConfiguration {
    @Override
    public String getDatabaseName() {
        return "mongo-test";
    }

    @Override
    @Bean
    public Mongo mongo() throws Exception {
        log.info("Creating Fake Mongo instance");
        return new Fongo("mongo-test").getMongo();
    }

    @Bean
    @Override
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongo(), getDatabaseName());
    }

}

使用此配置,您可以测试使用 Spring 上下文中的 MongoTemplate 的代码,并与 nosql-unitjsonunit 等,您将获得强大的单元测试涵盖 mongo 查询代码。

@Test
@UsingDataSet(locations = {"/TSDR1326-data/TSDR1326-subject.json"}, loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
@DatabaseSetup({"/TSDR1326-data/dbunit-TSDR1326.xml"})
public void shouldCleanUploadSubjectCollection() throws Exception {
    //given
    JobParameters jobParameters = new JobParametersBuilder()
            .addString("studyId", "TSDR1326")
            .addString("execId", UUID.randomUUID().toString())
            .toJobParameters();

    //when
    //next line runs a Spring Batch ETL process loading data from SQL DB(H2) into Mongo
    final JobExecution res = jobLauncherTestUtils.launchJob(jobParameters);

    //then
    assertThat(res.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
    final String resultJson = mongoTemplate.find(new Query().with(new Sort(Sort.Direction.ASC, "topLevel.subjectId.value")),
            DBObject.class, "subject").toString();

    assertThatJson(resultJson).isArray().ofLength(3);
    assertThatDateNode(resultJson, "[0].topLevel.timestamp.value").isEqualTo(res.getStartTime());

    assertThatNode(resultJson, "[0].topLevel.subjectECode.value").isStringEqualTo("E01");
    assertThatDateNode(resultJson, "[0].topLevel.subjectECode.timestamp").isEqualTo(res.getStartTime());

    ... etc
}

我使用 fakemongo 时没有遇到 mongo 3.4 驱动程序的问题,并且社区非常接近发布支持 3.6 驱动程序的版本(https://github.com/fakemongo/fongo/issues/316)。

I'm surprised no one advised to use fakemongo so far. It emulates mongo client pretty well, and it all runs on same JVM with tests - so integration tests become robust, and technically much more close to true "unit tests", since no foreign system interaction takes place. It's like using embedded H2 to unit test your SQL code.
I was very happy using fakemongo in unit tests that test database integration code in end-to-end manner. Consider this configuration in test spring context:

@Configuration
@Slf4j
public class FongoConfig extends AbstractMongoConfiguration {
    @Override
    public String getDatabaseName() {
        return "mongo-test";
    }

    @Override
    @Bean
    public Mongo mongo() throws Exception {
        log.info("Creating Fake Mongo instance");
        return new Fongo("mongo-test").getMongo();
    }

    @Bean
    @Override
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongo(), getDatabaseName());
    }

}

With this you can test your code that uses MongoTemplate from spring context, and in combination with nosql-unit, jsonunit, etc. you get robust unit tests that cover mongo querying code.

@Test
@UsingDataSet(locations = {"/TSDR1326-data/TSDR1326-subject.json"}, loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
@DatabaseSetup({"/TSDR1326-data/dbunit-TSDR1326.xml"})
public void shouldCleanUploadSubjectCollection() throws Exception {
    //given
    JobParameters jobParameters = new JobParametersBuilder()
            .addString("studyId", "TSDR1326")
            .addString("execId", UUID.randomUUID().toString())
            .toJobParameters();

    //when
    //next line runs a Spring Batch ETL process loading data from SQL DB(H2) into Mongo
    final JobExecution res = jobLauncherTestUtils.launchJob(jobParameters);

    //then
    assertThat(res.getExitStatus()).isEqualTo(ExitStatus.COMPLETED);
    final String resultJson = mongoTemplate.find(new Query().with(new Sort(Sort.Direction.ASC, "topLevel.subjectId.value")),
            DBObject.class, "subject").toString();

    assertThatJson(resultJson).isArray().ofLength(3);
    assertThatDateNode(resultJson, "[0].topLevel.timestamp.value").isEqualTo(res.getStartTime());

    assertThatNode(resultJson, "[0].topLevel.subjectECode.value").isStringEqualTo("E01");
    assertThatDateNode(resultJson, "[0].topLevel.subjectECode.timestamp").isEqualTo(res.getStartTime());

    ... etc
}

I used fakemongo without problems with mongo 3.4 driver, and community is really close to release a version that supports 3.6 driver (https://github.com/fakemongo/fongo/issues/316).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文