如何为 Django 项目添加单元测试

作者选择了开放互联网/自由言论基金作为为国家写作计划的一部分接受捐赠。

简介

要想建立的网站第一次就能完美运行而不出错,几乎是不可能的。因此,您需要对网络应用程序进行测试,以便发现这些错误并积极加以解决。为了提高测试的效率,通常会将测试分成若干单元,以测试网络应用程序的特定功能。这种做法称为单元测试。由于测试集中于项目的小部分(单元),与其他部分无关,因此更容易发现错误。

测试网站可能是一项复杂的任务,因为网站是由处理 HTTP 请求、表单验证和渲染模板等多层逻辑组成的。不过,Django 提供了一套工具,可以让你无缝地测试你的网络应用程序。在 Django 中,编写测试的首选方法是使用 Python unittest 模块,当然也可以使用其他测试框架。

在本教程中,您将在 Django 项目中建立一个测试套件,并为应用程序中的模型和视图编写单元测试。您将运行这些测试,分析其结果,并学习如何查找测试失败的原因。

先决条件

在开始本教程之前,您需要准备以下材料:

第 1 步 - 为 Django 应用程序添加测试套件

Django 中的测试套件是项目中所有应用程序中所有测试用例的集合。为了让 Django 测试工具发现您的测试用例,您需要将测试用例写入名称以 test 开头的脚本中。在此步骤中,您将为测试套件创建目录结构和文件,并在其中创建一个空测试用例。

如果您学习了 Django 开发 系列教程,您将拥有一个名为 blogsite 的 Django 应用程序。

让我们创建一个文件夹来存放所有测试脚本。首先,激活虚拟环境:

1cd ~/my_blog_app
2. env/bin/activate

然后导航到 blogsite 应用程序目录,即包含 models.pyviews.py 文件的文件夹,然后新建一个名为 tests 的文件夹:

1cd ~/my_blog_app/blog/blogsite
2mkdir tests

接下来,你要把这个文件夹变成一个 Python 包,所以要添加一个 __init__.py 文件:

1cd ~/my_blog_app/blog/blogsite/tests
2touch __init__.py

现在,您将添加一个用于测试模型的文件和另一个用于测试视图的文件:

1touch test_models.py
2touch test_views.py

最后,您将在 test_models.py 中创建一个空测试用例。您需要导入 Django TestCase,并使其成为您自己的测试用例类的超类。稍后,您将为该测试用例添加方法,以测试模型中的逻辑。打开文件 test_models.py

1nano test_models.py

现在在文件中添加以下代码:

1[label ~/my_blog_app/blog/blogsite/tests/test_models.py]
2from django.test import TestCase
3
4class ModelsTestCase(TestCase):
5    pass

现在,您已成功地为 blogsite 应用程序添加了一个测试套件。接下来,您将填写在此创建的空模型测试用例的详细信息。

第 2 步 - 测试 Python 代码

在这一步中,您将测试在 models.py 文件中编写的代码的逻辑。特别是,您将测试 Post 模型的 save 方法,以确保它在调用时创建帖子标题的正确标签。

首先,让我们看看您的 models.py 文件中已有的 Post 模型的 save 方法代码:

1cd ~/my_blog_app/blog/blogsite
2nano models.py

您将看到以下内容:

1[label ~/my_blog_app/blog/blogsite/models.py]
2class Post(models.Model):
3    ...
4    def save(self, *args, **kwargs):
5        if not self.slug:
6            self.slug = slugify(self.title)
7        super(Post, self).save(*args, **kwargs)
8    ...

我们可以看到,它会检查即将保存的帖子是否有 slug 值,如果没有,则调用 slugify 为其创建 slug 值。您可能需要测试这种类型的逻辑,以确保在保存文章时确实创建了 slug。

关闭文件。

要测试这一点,请返回 test_models.py

1nano test_models.py

然后将其更新为以下内容,并添加突出显示的部分:

 1[label ~/my_blog_app/blog/blogsite/tests/test_models.py]
 2from django.test import TestCase
 3from django.template.defaultfilters import slugify
 4from blogsite.models import Post
 5
 6class ModelsTestCase(TestCase):
 7    def test_post_has_slug(self):
 8        """Posts are given slugs correctly when saving"""
 9        post = Post.objects.create(title="My first post")
10
11        post.author = "John Doe"
12        post.save()
13        self.assertEqual(post.slug, slugify(post.title))

这个新方法 test_post_has_slug 创建了一个标题为 "我的第一篇文章" 的新文章,然后为文章指定了作者并保存了文章。然后,使用 Python unittest 模块中的 assertEqual 方法检查帖子的标签是否正确。assertEqual 方法会检查传递给它的两个参数是否相等(由 "=="运算符决定),如果不相等,就会引发错误。

保存并退出 test_models.py

这是一个可以测试的示例。添加到项目中的逻辑越多,需要测试的内容就越多。如果您在 save 方法中添加了更多逻辑,或为 Post 模型创建了新方法,您就需要在这里添加更多测试。您可以将它们添加到 test_post_has_slug 方法或创建新的测试方法,但它们的名称必须以 test 开头。

您已成功地为 Post 模型创建了一个测试用例,在该测试用例中,您断言保存后会正确地创建连接词。下一步,您将编写一个测试用例来测试视图。

第 3 步 - 使用 Django 的测试客户端

在此步骤中,您将编写一个测试用例,使用 Django 测试客户端测试视图。测试客户端](https://docs.djangoproject.com/en/3.0/topics/testing/tools/) 是一个 Python 类,它可以充当一个虚拟的 Web 浏览器,允许您测试视图,并以与用户相同的方式与 Django 应用程序交互。您可以在测试方法中引用 self.client 来访问测试客户端。例如,让我们在 test_views.py 中创建一个测试用例。首先,打开 test_views.py 文件:

1nano test_views.py

然后添加以下内容:

1[label ~/my_blog_app/blog/blogsite/tests/test_views.py]
2from django.test import TestCase
3
4class ViewsTestCase(TestCase):
5    def test_index_loads_properly(self):
6        """The index page loads properly"""
7        response = self.client.get('your_server_ip:8000')
8        self.assertEqual(response.status_code, 200)

ViewsTestCase "包含一个 "test_index_loads_properly "方法,该方法使用 Django 测试客户端访问网站的索引页面("http://your_server_ip:8000",其中 "your_server_ip "是您使用的服务器的 IP 地址)。然后,测试方法会检查响应的状态代码是否为 200,这意味着页面响应没有任何错误。因此,您可以确信,当用户访问时,它也会无错响应。

除了状态代码,您还可以在 Django 文档测试响应页面 阅读测试客户端响应的其他属性。

在这一步中,你创建了一个测试用例,用于测试渲染索引页面的视图是否能正常工作而不会出错。现在测试套件中有两个测试用例。下一步将运行它们,查看结果。

第 4 步 - 运行测试

现在,您已经为项目构建了一套测试,是时候执行这些测试并查看其结果了。要运行测试,请导航到 blog 文件夹(其中包含应用程序的 manage.py 文件):

1cd ~/my_blog_app/blog

然后用

1python manage.py test

您将在终端看到类似下面的输出:

1[secondary_label Output]
2Creating test database for alias 'default'...
3System check identified no issues (0 silenced).
4..
5----------------------------------------------------------------------
6Ran 2 tests in 0.007s
7
8OK
9Destroying test database for alias 'default'...

在此输出中,有两个点 ...,每个点代表一个通过的测试用例。现在要修改 test_views.py 以触发一个失败的测试。首先用以下命令打开文件

1nano test_views.py

然后将突出显示的代码更改为

1[label ~/my_blog_app/blog/blogsite/tests/test_views.py]
2from django.test import TestCase
3
4class ViewsTestCase(TestCase):
5    def test_index_loads_properly(self):
6        """The index page loads properly"""
7        response = self.client.get('your_server_ip:8000')
8        self.assertEqual(response.status_code, 404)

在此,您已将状态代码从 200 更改为 404。现在使用 manage.py,再次从您的目录运行测试:

1python manage.py test

您将看到以下输出:

 1[secondary_label Output]
 2Creating test database for alias 'default'...
 3System check identified no issues (0 silenced).
 4.F
 5======================================================================
 6FAIL: test_index_loads_properly (blogsite.tests.test_views.ViewsTestCase)
 7The index page loads properly
 8----------------------------------------------------------------------
 9Traceback (most recent call last):
10  File "~/my_blog_app/blog/blogsite/tests/test_views.py", line 8, in test_index_loads_properly
11    self.assertEqual(response.status_code, 404)
12AssertionError: 200 != 404
13
14----------------------------------------------------------------------
15Ran 2 tests in 0.007s
16
17FAILED (failures=1)
18Destroying test database for alias 'default'...

你会看到一条描述性的失败消息,告诉你失败的脚本、测试用例和方法。它还会告诉你失败的原因,在本例中状态代码不等于 404,信息为 AssertionError: 200 != 404。这里的 AssertionError 是在 test_views.py 文件中高亮显示的代码行引发的:

1[label ~/my_blog_app/blog/blogsite/tests/test_views.py]
2from django.test import TestCase
3
4class ViewsTestCase(TestCase):
5    def test_index_loads_properly(self):
6        """The index page loads properly"""
7        response = self.client.get('your_server_ip:8000')
8        self.assertEqual(response.status_code, 404)

它告诉你断言是错误的,也就是说,响应状态代码 (200)与预期 (404)不同。在失败消息之前,您可以看到两个点 .. 现在变成了 .F,这说明第一个测试用例通过了,而第二个测试用例没有通过。

结论

在本教程中,您在 Django 项目中创建了测试套件,添加了测试用例以测试模型和视图逻辑,学习了如何运行测试,并分析了测试输出。下一步,您可以为不在 models.pyviews.py 中的 Python 代码创建新的测试脚本。

以下是一些在使用 Django 构建和测试网站时可能会有所帮助的文章:

您还可以查看我们的 Django 主题页面 了解更多教程和项目。

Published At
Categories with 技术
comments powered by Disqus