用于测试简单MongoDB相关代码的伪pymongo存根
项目描述
这是什么?
Mongomock是一个小型库,用于帮助测试通过Pymongo与MongoDB交互的Python代码。
要了解它的用途,我们可以参考以下代码
def increase_votes(collection):
for document in collection.find():
collection.update_one(document, {'$set': {'votes': document['votes'] + 1}})
上述代码可以通过几种方式进行测试
可以使用pymongo测试真实mongodb实例。
它可以接收一个记录-回放风格的模拟作为参数。这样,我们记录预期的调用(查找,然后一系列更新),并在稍后重新播放。
它可以接收精心制作的模拟响应,以适当地处理 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 库已经发展到帮助您轻松迁移
升级到 Mongomock v4 或更高版本:如果您的测试正在使用已安装的 Pymongo 运行,Mongomock 将将其自己的 API 与已安装的 Pymongo 版本相适应。
升级到 Pymongo v4 或更高版本:使用 Mongomock 的测试将失败的地方与您的代码在生产中失败的地方完全一样,这样您可以在发布之前修复它。
贡献
在提交 PR 时,请确保
您为您添加的功能或修复的 bug 包括测试。最好,该测试应与真实的 MongoDB 引擎进行比较(参考测试中的示例)。
没有现有的测试被删除或意外破坏
您的 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
Julian Hille
Krzysztof Płocharz
Lyon Zhang
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
Zachary Carter
catty (ca77y _at_ live.com)
emosenkis
hthieu1110
יppetlinskiy
pacud
tipok
waskew (waskew _at_ narrativescience.com)
jmsantorum (jmsantorum [at] gmail [dot] com)
lidongyong