单元测试检查数据库查询是否正确——可以模拟什么?
Unit test checking if database queries are correct – what can be mocked?
假设我有一个 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
查询的正确性(它可能会变得非常复杂),所以我不想猜测 QuerySet
数据库会给我什么。
理想情况下,我想使用这样的东西(伪代码如下):
johns_article = Article(author="John Lennon")
fake_query = MockQuery(author__contains="John")
assertTrue(fakeQuery.contains(johns_article))
accessing database is frowned upon in favor of mocking things
这是为什么??常见的问题是:
- 测试速度
- 测试可靠性
- 测试片状
- 测试并行化
- 夹具创建
- 一般数据管理
如果您需要测试数据库交互,唯一的方法是 运行 针对数据库进行测试。 Django 已涵盖并创建了一个框架来解决上面列出的所有问题。 django TestCase
为您处理所有这些。它管理一个测试数据库,提供配置工具,在事务中执行所有测试,并通过在每个事务后回滚来快速清理。
我绝对同意,对于快速测试,它们应该在单元级别完成,并存根所有协作和 file/socket 访问,但在这种情况下,django 应该可以满足您的要求。
您已经在使用 django 数据库,每个测试都会配置该数据库 运行,为什么不访问它呢?
所以基本上你的测试是正确的,django 为它创建了工具,它根本不是反模式。一个常见的策略是进行多层测试。您可以有一个 "unit test" 或 "small test" 框架和测试 运行ner,它不执行大部分测试所在的 IO,所有协作都是 mocked/stubbed。然后在django中实现多项测试TestCase
测试数据库交互
如果您从 testing/django 开始,那么在长期 运行 中有几件事可以提供帮助:
- 分析您的查询,
get_beatles_articles_titles
可以优化并用更少的查询完成
- 使用
bulk_create
方法而不是多个 create
方法
- 使用抽象来实例化您的测试模型,我最喜欢的是工厂男孩。这将帮助您避免更改创建模型所需的内容,而且它允许您创建合理的动态默认值,因此创建过程更加简洁。
假设我有一个 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
查询的正确性(它可能会变得非常复杂),所以我不想猜测 QuerySet
数据库会给我什么。
理想情况下,我想使用这样的东西(伪代码如下):
johns_article = Article(author="John Lennon")
fake_query = MockQuery(author__contains="John")
assertTrue(fakeQuery.contains(johns_article))
accessing database is frowned upon in favor of mocking things
这是为什么??常见的问题是:
- 测试速度
- 测试可靠性
- 测试片状
- 测试并行化
- 夹具创建
- 一般数据管理
如果您需要测试数据库交互,唯一的方法是 运行 针对数据库进行测试。 Django 已涵盖并创建了一个框架来解决上面列出的所有问题。 django TestCase
为您处理所有这些。它管理一个测试数据库,提供配置工具,在事务中执行所有测试,并通过在每个事务后回滚来快速清理。
我绝对同意,对于快速测试,它们应该在单元级别完成,并存根所有协作和 file/socket 访问,但在这种情况下,django 应该可以满足您的要求。
您已经在使用 django 数据库,每个测试都会配置该数据库 运行,为什么不访问它呢?
所以基本上你的测试是正确的,django 为它创建了工具,它根本不是反模式。一个常见的策略是进行多层测试。您可以有一个 "unit test" 或 "small test" 框架和测试 运行ner,它不执行大部分测试所在的 IO,所有协作都是 mocked/stubbed。然后在django中实现多项测试TestCase
测试数据库交互
如果您从 testing/django 开始,那么在长期 运行 中有几件事可以提供帮助:
- 分析您的查询,
get_beatles_articles_titles
可以优化并用更少的查询完成 - 使用
bulk_create
方法而不是多个create
方法 - 使用抽象来实例化您的测试模型,我最喜欢的是工厂男孩。这将帮助您避免更改创建模型所需的内容,而且它允许您创建合理的动态默认值,因此创建过程更加简洁。