跳转到主要内容

用于测试简单MongoDB相关代码的伪pymongo存根

项目描述

https://img.shields.io/pypi/v/mongomock.svg?style=flat-square https://img.shields.io/github/actions/workflow/status/mongomock/mongomock/lint-and-test.yml?branch=develop&style=flat-square https://img.shields.io/pypi/l/mongomock.svg?style=flat-square https://img.shields.io/codecov/c/github/mongomock/mongomock.svg?style=flat-square

这是什么?

Mongomock是一个小型库,用于帮助测试通过Pymongo与MongoDB交互的Python代码。

要了解它的用途,我们可以参考以下代码

def increase_votes(collection):
    for document in collection.find():
        collection.update_one(document, {'$set': {'votes': document['votes'] + 1}})

上述代码可以通过几种方式进行测试

  1. 可以使用pymongo测试真实mongodb实例。

  2. 它可以接收一个记录-回放风格的模拟作为参数。这样,我们记录预期的调用(查找,然后一系列更新),并在稍后重新播放。

  3. 它可以接收精心制作的模拟响应,以适当地处理 find() 和 update()。

在这里,选项 1 显然是最佳方法,因为我们正在对实际的 MongoDB 实例进行测试。然而,为此需要设置 MongoDB 实例,并在测试前后进行清理。您可能想在持续集成服务器、笔记本电脑或其他奇怪的平台上的测试 - 这使得 MongoDB 要求成为一项负债。

我们只剩下 #2 和 #3。不幸的是,在真实场景中它们维护成本很高,因为它们复制了代码中调用的一系列操作,违反了 DRY 规则。让我们看看 #2 的实际应用 - 我们可能会这样编写测试

def test_increase_votes():
    objects = [dict(...), dict(...), ...]
    collection_mock = my_favorite_mock_library.create_mock(Collection)
    record()
    collection_mock.find().AndReturn(objects)
    for obj in objects:
        collection_mock.update_one(obj, {'$set': {'votes': obj['votes']}})
    replay()
    increase_votes(collection_mock)
    verify()

假设有一天代码发生了变化,因为作者刚刚了解到 '$inc' 指令

def increase_votes(collection):
    collection.update_many({}, {'$inc': {'votes': 1}})

这破坏了测试,尽管被测试的最终结果是一样的。测试还重复了我们已经编写的代码的大部分内容。

因此,我们只剩下选项 #3 – 您想要某种东西的行为类似于 MongoDB 数据库集合,但实际上不是。这正是这个库旨在提供的。使用 mongomock,测试简单变为

def test_increase_votes():
    collection = mongomock.MongoClient().db.collection
    objects = [dict(votes=1), dict(votes=2), ...]
    for obj in objects:
        obj['_id'] = collection.insert_one(obj).inserted_id
    increase_votes(collection)
    for obj in objects:
        stored_obj = collection.find_one({'_id': obj['_id']})
        stored_obj['votes'] -= 1
        assert stored_obj == obj # by comparing all fields we make sure only votes changed

此代码检查 increase_votes 的功能,而不是语法或算法,因此作为测试更加健壮。

如果待测试的代码正在使用 pymongo 创建连接,您可以使用 mongomock.patch(注意:您应该使用 pymongo.MongoClient(...) 而不是 from pymongo import MongoClient,如下所示)

@mongomock.patch(servers=(('server.example.com', 27017),))
def test_increate_votes_endpoint():
  objects = [dict(votes=1), dict(votes=2), ...]
  client = pymongo.MongoClient('server.example.com')
  client.db.collection.insert_many(objects)
  call_endpoint('/votes')
  ... verify client.db.collection

关于项目状态和开发的重要说明

MongoDB 很复杂。这个库旨在为测试目的提供一个相对完整的 MongoDB 模拟,而不是一个完美的副本。这意味着一些功能可能不会很快出现。

此外,由于在开发过程中遇到了许多边缘情况,我们的目标是尝试通过 TDD 路径达到完整性。这意味着每次我们遇到缺失或损坏(不兼容)的功能时,我们都会为它编写一个测试并修复它。可能有很多这样的问题潜伏在周围,所以请随时打开问题/拉取请求,帮助项目。

注意:我们不包含 pymongo 功能作为“存根”或“占位符”。由于这个库用于验证生产代码,其行为与真实的 pymongo 实现不同是不可接受的。在这种情况下,最好是抛出 NotImplementedError 而不是实现原始行为的修改版本。

升级到 Pymongo v4

Pymongo 的主要版本 4 改变了 API。Mongomock 库已经发展到帮助您轻松迁移

  1. 升级到 Mongomock v4 或更高版本:如果您的测试正在使用已安装的 Pymongo 运行,Mongomock 将将其自己的 API 与已安装的 Pymongo 版本相适应。

  2. 升级到 Pymongo v4 或更高版本:使用 Mongomock 的测试将失败的地方与您的代码在生产中失败的地方完全一样,这样您可以在发布之前修复它。

贡献

在提交 PR 时,请确保

  1. 您为您添加的功能或修复的 bug 包括测试。最好,该测试应与真实的 MongoDB 引擎进行比较(参考测试中的示例)。

  2. 没有现有的测试被删除或意外破坏

  3. 您的 PR 上的构建通过。

要在 Mac/Linux 上下载、设置和执行测试,请运行以下命令

$ git clone git@github.com:mongomock/mongomock.git
$ pipx install hatch
$ cd mongomock
$ hatch test

或者,可以使用 docker-compose 来简化本地开发中的依赖关系管理

$ git clone git@github.com:mongomock/mongomock.git
$ cd mongomock
$ docker compose build
$ docker compose run --rm mongomock

如果您想在容器中针对特定环境运行 hatch

$ docker compose run --rm mongomock hatch test -py=3.11 -i pymongo=4

如果您只想运行一个测试,您也可以在命令末尾添加测试名称

$ docker compose run --rm mongomock hatch test -py=3.12 -i pymongo=4 tests/test__mongomock.py::MongoClientCollectionTest::test__insert

注意:如果MongoDB镜像已更新,或者您想在docker-compose中尝试不同的MongoDB版本,您必须在执行其他任何操作之前先执行docker compose down,以确保您运行的是期望的版本。

utcnow

当开发需要使用“现在”的功能时,请使用以下方式使用库中的utcnow辅助方法:

import mongomock
# Awesome code!
now_reference = mongomock.utcnow()

这为用户提供了在mongomock中模拟“现在”概念的一致方法。有关详细信息,请参阅utcnow的docstring

分支模型

此项目使用的分支模型遵循gitflow工作流程。这意味着应该针对develop分支而不是master分支提交拉取请求。如果您想为遗留的2.x分支做出贡献,则您的拉取请求应进入support/2.x分支。

发布

准备发布时,使用新标签标记develop分支(请保留semver名称),并将您的标签推送到GitHub。CI将完成其余工作。

要添加发布说明,请先在GitHub的Releases页面上创建一个发布,然后使用以下命令在本地生成发布说明:

python -c "from pbr import git; git.write_git_changelog()"

然后您可以在生成的Changelog文件中找到相关部分。

致谢

Mongomock最初由Rotem Yaari开发,然后由Martin Domke开发。它目前由Pascal Corpet开发和维护。

此外,许多人对以下人的帮助表示感谢,他们帮助贡献拉取请求和修复错误

  • Alec Perkins

  • Alexandre Viau

  • Austin W Ellis

  • Andrey Ovchinnikov

  • Arthur Hirata

  • Baruch Oxman

  • Corey Downing

  • Craig Hobbs

  • Daniel Murray

  • David Fischer

  • Diego Garcia

  • Dmitriy Kostochko

  • Drew Winstel

  • Eddie Linder

  • Edward D’Souza

  • Emily Rosengren

  • Eugene Chernyshov

  • Grigoriy Osadchenko

  • Israel Teixeira

  • Jacob Perkins

  • Jason Burchfield

  • Jason Sommer

  • Jeff Browning

  • Jeff McGee

  • Joël Franusic

  • Jonathan Hedén

  • Julian Hille

  • Krzysztof Płocharz

  • Lyon Zhang

  • Lucas Rangel Cezimbra

  • Marc Prewitt

  • Marcin Barczynski

  • Marian Galik

  • Michał Albrycht

  • Mike Ho

  • Nigel Choi

  • Omer Gertel

  • Omer Katz

  • Papp Győző

  • Paul Glass

  • Scott Sexton

  • Srinivas Reddy Thatiparthy

  • Taras Boiko

  • Todd Tomkinson

  • Xinyan Lu

  • Zachary Carter

  • catty (ca77y _at_ live.com)

  • emosenkis

  • hthieu1110

  • יppetlinskiy

  • pacud

  • tipok

  • waskew (waskew _at_ narrativescience.com)

  • jmsantorum (jmsantorum [at] gmail [dot] com)

  • lidongyong

  • Juan Gutierrez

由以下支持