Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/375.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 使用MongoDB进行单元测试_Java_Unit Testing_Mongodb_Junit_Morphia - Fatal编程技术网

Java 使用MongoDB进行单元测试

Java 使用MongoDB进行单元测试,java,unit-testing,mongodb,junit,morphia,Java,Unit Testing,Mongodb,Junit,Morphia,我选择的数据库是MongoDB。我正在编写一个数据层API来从客户端应用程序中抽象实现细节——也就是说,我本质上提供了一个公共接口(一个充当IDL的对象) 我以TDD的方式测试我的逻辑。在每个单元测试之前,调用@Before方法来创建数据库单例,之后,当测试完成时,调用@after方法来删除数据库。这有助于促进单元测试之间的独立性 几乎所有的单元测试,即执行上下文查询,都需要某种插入逻辑才能进行。我的公共接口提供了一个插入方法——然而,将此方法用作每个单元测试的前置逻辑似乎是不正确的 实际上,我

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

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

几乎所有的单元测试,即执行上下文查询,都需要某种插入逻辑才能进行。我的公共接口提供了一个插入方法——然而,将此方法用作每个单元测试的前置逻辑似乎是不正确的

实际上,我需要某种模拟机制,然而,我还没有太多的模拟框架的经验,而且谷歌似乎没有返回任何东西,这是一个可以与MongoDB一起使用的模拟框架

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


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

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

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

在测试与Mongo对话的服务实现时(例如MongoUserService),最简单的方法是编写一些java代码来启动/停止本地机器上的Mongo进程,并让您的MongoUserService连接到该进程,请参见此


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

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

我所做的另一种方法是创建Mongo对象的模拟(例如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));

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

我用Java编写了一个MongoDB伪实现:

默认值是内存中的后端,可以轻松地用于单元测试和集成测试

例子
我很惊讶到目前为止没有人建议使用。它很好地模拟了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的代码,并将其与等结合使用。您可以获得涵盖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驱动程序()的版本。

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


这里需要PowerMockito吗?在这种情况下,看起来像是笔直的Mockito(或EasyMock)将完成这项工作。是的,你是对的。莫基托就足够了。我们在几个地方使用PowerMockito,这就是为什么我刚刚用PowerMockito编写了这个示例。“莫基托也应该没问题。”拉曼,我相信你的评论是错误的。我们发现MongoAPI大量使用final,例如不允许我们去掉findOne方法。对我们来说,只有PowerMockito方法起作用。还要注意,从Mongo3.x开始,getDB()和getCollection()都是不推荐的,所以您需要这样做:MongoClient Mongo=Mockito.mock(MongoClient.class);MongoDatabase db=Mockito.mock(MongoDatabase.class);MongoCollection dbCollection=Mockito.mock(MongoCollection.class);希望现在有人正在为MongoDB——MongoUnit——发明一个类似DbUnit的框架……我还没有尝试过这个,但请查看:非常有用的答案。但我必须承认,其中对DbUnit的引用有点误导:因为DbUnit目前只支持关系数据库,不支持MongoDB。我加入了@Raman的行列:)这些类型的赝品并不是特别健壮,因为它们可能与实际实现相差太远。比运行真正的integration DB实例更快、更简单,对吗?:)如果使用小型测试数据集运行,那么DB交互代码所需的时间远远少于Spring测试上下文初始化所需的时间。在一些项目中,我看到这种测试在100-200毫秒内通过,还不错。而且它节省了大量的开发工作,因为您不需要在试图嘲笑y时胡闹
@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
}
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"));
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)