单元测试检查数据库查询是不是正确——可以模拟啥?

Posted

技术标签:

【中文标题】单元测试检查数据库查询是不是正确——可以模拟啥?【英文标题】:Unit test checking if database queries are correct – what can be mocked?单元测试检查数据库查询是否正确——可以模拟什么? 【发布时间】:2016-12-18 01:59:59 【问题描述】:

假设我有一个Article 模型,如下所示:

from django.db import models

class Article(models.Model):
    author = models.CharField(max_length=100)
    title = models.CharField(max_length=200)
    body = models.TextField()

与我的实际使用相比,这非常简单(author 应该是另一个模型的ForeignKey,等等),但是这样更清楚。

假设我想列出某些作者的所有文章的标题,但将每个作者的文章放在一起。它可以表示为列表列表:

def get_beatles_articles_titles():
    beatles = [
        "John Lennon",
        "Paul McCartney",
        "George Harrison", 
        "Ringo Starrr",
    ]
    return [article.author for author in beatles 
            for article in Article.objects.filter(author=author)]

哦,嵌套列表推导式,所以我们的方法没那么简单。这里的某个地方很有可能存在错误,所以我们应该以某种方式对其进行测试!最简单的解决方案似乎是为每个作者创建一些 Article 实例(并将它们保存在数据库中)并检查它们是否都被正确获取。

from django.test import TestCase

from models import Article
from views import get_beatles_articles_titles

class ArticlesTitlesTestCase(TestCase):
    def test_that_every_beatles__article_is_fetched(self):
        Article.objects.create(author="John Lennon", title="John's")
        Article.objects.create(author="Paul McCartney", title="Paul's")
        Article.objects.create(author="George Harrison", title="George's")
        Article.objects.create(author="Ringo Starr", title="Ringo's")

        self.assertEqual(get_beatles_articles_titles(), [
            ["John's"],
            ["Paul's"],
            ["George's"],
            ["Ringo's"]
        ])

运行该测试,我们可以看到我们的原始代码中有一个错字,因此证明了它的用处。

但是,访问数据库不赞成模拟事物(难怪,我经历过时间差异可能很大)。在上面的测试中可以嘲笑什么?我特别担心我的.filter 查询的正确性(它可能会变得相当复杂),所以我不想猜测QuerySets 数据库会给我。

理想情况下,我想使用这样的东西(伪代码如下):

johns_article = Article(author="John Lennon")
fake_query = MockQuery(author__contains="John")
assertTrue(fakeQuery.contains(johns_article))

【问题讨论】:

【参考方案1】:

访问数据库不赞成模拟事物

这是为什么?常见的担忧是:

测试速度 测试可靠性 测试片状 测试并行化 夹具创建 一般数据管理

如果您需要测试数据库交互,唯一的方法是针对数据库运行测试。 Django 为您提供并创建了一个框架来解决上面列出的所有问题。 django TestCase 为您处理所有这些。它管理一个测试数据库,提供用于配置的工具,执行事务中的所有测试,并通过在每个事务后回滚来快速清理。

我绝对同意,对于快速测试,它们应该在单元级别完成,并存根所有协作和文件/套接字访问,但在这种情况下,django 应该涵盖您。

您已经在使用 django 数据库,它会为每次测试运行配置,那么为什么不访问它呢?

所以基本上你的测试是正确的,django 为它创建了工具,它根本不是一个反模式。一个常见的策略是进行多层测试。你可以有一个“单元测试”或“小测试”框架和测试运行器,它在大多数测试都存在的地方没有 IO,所有协作都被模拟/存根。然后在djangoTestCase中实现一些测试来测试数据库交互。


如果您从 testing/django 开始,从长远来看,有几件事可能会有所帮助:

    分析您的查询,get_beatles_articles_titles 可以通过更少的查询进行优化和完成 使用bulk_create 方法而不是多个create 方法 使用抽象来实例化您的测试模型,我最喜欢的是工厂男孩。这将帮助您避免更改创建模型所需的内容,此外它还允许您创建健全的动态默认值,因此创建过程更加简洁。

【讨论】:

感谢提醒bulk_create。坦率地说,我希望有一天能看到这样的功能,但感谢您解释为什么没有那么多必要!

以上是关于单元测试检查数据库查询是不是正确——可以模拟啥?的主要内容,如果未能解决你的问题,请参考以下文章

Unity 单元测试 - 检查带有参数的函数调用是不是引发异常

Right-BICEP单元测试

SQL命令未正确结束是啥原因,去掉;也不行

实体框架是不是有内存提供程序?

如何编写单元测试以检查单击某个链接后是否显示模态

DAO(又名存储库)是不是应该进行单元测试?