在 node.js 中模拟数据库?

Posted

技术标签:

【中文标题】在 node.js 中模拟数据库?【英文标题】:Mocking database in node.js? 【发布时间】:2012-09-13 14:50:24 【问题描述】:

如何在我的 node.js 应用程序中模拟数据库,在本例中使用 mongodb 作为博客 REST API 的后端?

当然,我可以将数据库设置为特定的testing -database,但我仍然会保存数据而不是只测试我的代码,还要测试数据库,所以我实际上不是在做单元测试,而是在做集成测试。 那么一个人应该怎么做呢?创建数据库包装器作为应用程序和数据库之间的中间层并在测试时替换 DAL?

// app.js  
var express = require('express');
    app = express(),
    mongo = require('mongoskin'),
    db = mongo.db('localhost:27017/test?auto_reconnect');

app.get('/posts/:slug', function(req, res)
    db.collection('posts').findOne(slug: req.params.slug, function (err, post) 
        res.send(JSON.stringify(post), 200);
    );
);

app.listen(3000);

// test.js
r = require('requestah')(3000);
describe("Does some testing", function() 

  it("Fetches a blogpost by slug", function(done) 
    r.get("/posts/aslug", function(res) 
      expect(res.statusCode).to.equal(200);
      expect(JSON.parse(res.body)["title"]).to.not.equal(null);
      return done();
    );

  );
));

【问题讨论】:

【参考方案1】:

关于模拟,有一个通用的经验法则是

不要嘲笑你不拥有的任何东西。

如果您想模拟数据库,请将其隐藏在抽象服务层后面并模拟该层。然后确保您对实际的服务层进行集成测试。

就我个人而言,我已经不再使用模拟进行测试,而是将它们用于自上而下的设计,帮助我从上到下推动开发,在我进行的过程中模拟服务层,然后最终实现这些层并编写集成测试。用作测试工具时,它们往往会使您的测试变得非常脆弱,并且在最坏的情况下会导致实际行为和模拟行为之间出现差异。

【讨论】:

当然,您可以将其隐藏在存储库或网关后面,并使用模拟来驱动您的测试驱动方法并隔离您的单元测试......那你是什么意思你不使用模拟进行测试?您将在您的测试中保留那个模拟的网关/存储库,然后以某种方式使用接口通过接口指定存储库中的真实实现,对吗? 您如何处理第三方 API?类似 VCR 的助手? 你是最棒的先生!【参考方案2】:

模拟的目的是跳过复杂性和单元测试自己的代码。如果您想编写 e2e 测试,请使用 db.

编写代码来设置/拆除用于单元测试的测试数据库是技术债务,而且令人难以置信的不满意。

npm 中有 mock 库:

mongo - https://www.npmjs.com/package/mongomock

猫鼬-https://www.npmjs.com/package/mockgoose

如果那些不支持您需要的功能,那么是的,您可能需要使用真实的东西。

【讨论】:

mongo-mock 不支持聚合操作,例如这可能是我开始使用它的一个问题,最后我最终使用带有 testOnly 数据库的真实软件 另外,当从 mongo-mock 更改为 mongodb 时,很多东西都崩溃了,所以它破坏了目的【参考方案3】:

我遇到了这个难题,因此选择使用测试数据库并在每次测试开始时对其进行清理。 (如何删除所有内容:https://***.com/a/25639377/378594)

使用 NPM,您甚至可以创建一个测试脚本来创建 db 文件并在之后清理它。

【讨论】:

这实际上是一个不错的方法,我建议任何从事认真 CI/CD 流水线的人都这样做。使用今天的 javascript 工具,我们能够在运行测试之前和之后轻松创建/删除测试数据库。我会争论这种方法在开发时是否合适(您不想在每次代码更改时删除并创建数据库),但还有其他解决方案。至少它解决了必须维护单独的插件集成来模拟数据并将它们粘合到您的测试环境的问题。【参考方案4】:

到目前为止,我不同意所选答案或其他回复。

如果您能够在进行 QA 之前发现由对 DB 模式和您的代码进行的混乱且多次混乱的更改所产生的错误,那岂不是很棒?我敢打赌,大多数人会大喊是的!

您当然可以而且应该隔离和测试您的数据库模式。而且您不会基于模拟器或重图像或数据库和机器的重新创建来执行此操作。这就是像 SQLite 这样的东西的一个例子。您基于内存中轻量级实例运行并使用在内存实例中不会更改的静态数据来模拟它,这意味着您真正在隔离测试您的数据库,并且您也可以信任您的测试。显然它很快,因为它在内存中,是一个骨架,并且在测试运行结束时被废弃。

所以是的,您应该并且您应该测试导出到您正在使用的任何数据库引擎/运行时的非常轻量级的内存实例中的 SCHEMA,并且随着添加非常少量的静态数据成为您隔离的模拟数据库.

在每次推送到 QA 之前,您会定期(以自动方式)从您的真实数据库中导出您的真实架构,并将这些架构导入/更新到您的内存数据库实例中,如果您的数据库完成了任何最新的数据库更改,您将立即知道最近更改架构的管理员或其他开发人员破坏了所有测试。

虽然我对尽力回答的努力表示赞赏,但如果可以的话,我会否决当前的答案,但我是新手,还没有建立足够的声誉来使我有能力这样做。

至于回答“不要嘲笑任何你不拥有的东西”的人。我认为他的意思是“不要测试你不拥有的任何东西”。但是你会嘲笑你不拥有的东西!因为那些不是需要隔离的东西!

我计划与您分享 HOW,并将在未来的某个时间点用真实的示例 JS 代码更新这篇文章!

这是许多测试驱动团队一直在做的事情。您只需要了解方法即可。

【讨论】:

我很想投票,但如果它不能缓解问题并提供解决方案,我就不能投票。有机会请更新您的帖子。 2018 仍然没有“如何”,当然有兴趣阅读更多内容。 我投了反对票,因为 3 年后仍然没有。 6 年过去了,仍然没有关于如何...为什么让我们等这么久?正因为如此,我否决了这个答案,并赞成next one(现在在上面:)。当我们得到 HOW 时,我会重新考虑这个问题【参考方案5】:

我首选的对任何语言的 DB 代码进行单元测试的方法是通过 Repository 抽象访问 Mongo(这里有一个示例 http://iainjmitchell.com/blog/?p=884)。实现会因公开的 DB 特定功能而异,但通过从您自己的逻辑中删除所有 Mongo 代码,您就可以进行单元测试。只需将 Mongo Repository 实现替换为非常简单的存根版本即可。例如,只需将对象存储在一个简单的内存字典集合中。

通过这种方式,您将获得对自己的代码进行单元测试的好处,而无需依赖数据库,但您仍然需要对主数据库进行集成测试,因为您可能永远无法模拟真实数据库的特性正如其他人在这里所说的那样。我发现的事情就像在安全模式下与没有安全模式下的索引一样简单。具体来说,如果您有一个唯一索引,那么您的虚拟内存实现可能会在所有情况下都遵守这一点,但 Mongo 不会没有安全模式。

因此,尽管您仍需要针对数据库进行某些操作的测试,但您当然可以使用已存根的存储库实现正确地对您自己的逻辑进行单元测试。

【讨论】:

但是在某些时候你的真实实现代码必须引用真实的数据调用。我假设您将接口注入到您的存储库中以获取真正的数据层查询代码? 尝试使用 Docker。我通过使用 Docker 运行容器运行数据库,解决了多年的数据库和软件包安装方面的噩梦般的配置问题,这些数据库使用场景的特定测试数据初始化。事实上,我运行 3 个容器的堆栈:一个带有数据库,一个带有应用程序代码,一个带有测试驱动程序。如果您的数据集大小适中,您甚至可以启动这些堆栈的并行实例,从而大大缩短您的测试周期。【参考方案6】:

如果不使用数据库软件进行测试,我认为无法正确测试与数据库相关的代码。那是因为您要测试的代码不仅是 javascript,而且是数据库查询字符串。即使在您的情况下,查询看起来很简单,您也不能永远依赖它。

所以任何数据库模拟层都必须实现整个数据库(也许减去磁盘存储)。到那时,即使您将其称为单元测试,您最终还是会使用数据库模拟器进行集成测试。另一个缺点是,与数据库相比,数据库模拟器最终可能会出现一组不同的错误,并且您最终可能不得不为数据库模拟器和数据库编写代码(有点像 IE、Firefox 和 Chrome 等的情况。 )。

因此,在我看来,正确测试代码的唯一方法是将其与真实数据库进行交互。

【讨论】:

你知道,你说得很好。虽然单元测试具有非凡的目的(即隔离),但您已经为集成测试提出了一个强项。 @MichaelPerrenoud:我喜欢 christkv 的回答提出的规则:“不要嘲笑任何你不拥有的东西”。虽然它没有详细说明为什么这是一个坏主意,但这是一个容易记住的规则。 我不同意这个答案,在meteorjs中,他们在运行测试时以某种方式设置了一个测试数据库(我认为它不是一个模拟库,而是一个临时文件),而且非常方便。拥有一个行为与 mongodb 完全相同并自行清理的对象将非常有用。无论是全部在内存中还是临时文件中都是实现细节,因此您不必重复代码。我同意制作驱动程序的人应该是制作模拟对象的人。 我想我也不同意这个答案。 OP 的问题很简单,因为它只是一个控制器和数据库。那么所有使服务器成为服务器的应用程序逻辑,而不仅仅是从数据库到互联网的管道呢?假设您有一个要验证的对象,然后将其放入数据库中,是否可以想象诸如用于检查交互是否仍然有效的集成测试和用于检查所有验证的单元测试之类的东西?从字面上理解这个答案,如何使用源自数据库的数据来测试任何东西?您的构建需要多长时间?? 看,模拟数据库调用非常愚蠢,只会导致工作繁忙。我认为人们错过了测试的全部意义。这是为了显示输入匹配输出。对于调用数据库的函数,除非该测试涉及数据库调用,否则您无法确定这一点。最好的方法是让您的测试配置指向在内存上运行的 MongoDB 实例。这样您就不必处理磁盘 I/O,并且还实现了“模拟”的主要目的,即从内存中运行。

以上是关于在 node.js 中模拟数据库?的主要内容,如果未能解决你的问题,请参考以下文章

模拟/测试 Mongodb 数据库 Node.js

使用 node 模拟请求接口

Node.js 与 Typescript 和 Mysql 测试路由模拟服务

NO--12模拟服务器端请求之node.js

Typescript / Node.js - 如何模拟集成测试的传递依赖项?

Node.js-Koa2框架生态实战-从零模拟新浪微博 完整教程