如何将单元测试添加到您的Django项目中
作为 Write for DOnations 计划的一部分,作者选择了 Open Internet/Free Speech Fund 来接受捐赠。
介绍
几乎不可能建立第一次就完美运行而没有错误的网站。 因此,您需要测试您的 Web 应用程序以发现这些错误并主动处理它们。 为了提高测试效率,通常将测试分解为测试 Web 应用程序特定功能的单元。 这种做法称为单元测试。 它使检测错误变得更容易,因为测试专注于项目的小部分(单元),独立于其他部分。
测试网站可能是一项复杂的任务,因为它由多个逻辑层组成,例如处理 HTTP 请求、表单验证和呈现模板。 但是 Django 提供了一组工具,可以无缝地测试您的 Web 应用程序。 在 Django 中,编写测试的首选方法是使用 Python unittest 模块,尽管也可以使用其他测试框架。
在本教程中,您将在 Django 项目中设置测试套件,并为应用程序中的模型和视图编写单元测试。 您将运行这些测试,分析它们的结果,并学习如何找出测试失败的原因。
先决条件
在开始本教程之前,您需要以下内容:
- Django 安装在您的服务器上,并设置了编程环境。 为此,您可以遵循我们的 如何安装 Django Web 框架和设置编程环境 教程之一。
- 使用模型和视图创建的 Django 项目。 在本教程中,我们遵循了 Django Development 教程系列中的项目。
第 1 步 — 将测试套件添加到您的 Django 应用程序
Django 中的测试套件是项目中所有应用程序中所有测试用例的集合。 为了让 Django 测试实用程序能够发现您拥有的测试用例,您可以在名称以 test
开头的脚本中编写测试用例。 在这一步中,您将为您的测试套件创建目录结构和文件,并在其中创建一个空的测试用例。
如果您遵循 Django 开发 教程系列,您将拥有一个名为 blogsite
的 Django 应用程序。
让我们创建一个文件夹来保存我们所有的测试脚本。 首先,激活虚拟环境:
cd ~/my_blog_app . env/bin/activate
然后导航到 blogsite
应用程序目录,包含 models.py
和 views.py
文件的文件夹,然后创建一个名为 tests
的新文件夹:
cd ~/my_blog_app/blog/blogsite mkdir tests
接下来,您将把这个文件夹变成一个 Python 包,因此添加一个 __init__.py
文件:
cd ~/my_blog_app/blog/blogsite/tests touch __init__.py
您现在将添加一个用于测试模型的文件和另一个用于测试您的视图的文件:
touch test_models.py touch test_views.py
最后,您将在 test_models.py
中创建一个空的测试用例。 您需要导入 Django TestCase 类 并使其成为您自己的测试用例类的超类。 稍后,您将向此测试用例添加方法以测试模型中的逻辑。 打开文件test_models.py
:
nano test_models.py
现在将以下代码添加到文件中:
~/my_blog_app/blog/blogsite/tests/test_models.py
from django.test import TestCase class ModelsTestCase(TestCase): pass
您现在已成功将测试套件添加到 blogsite
应用程序。 接下来,您将填写您在此处创建的空模型测试用例的详细信息。
第 2 步——测试你的 Python 代码
在此步骤中,您将测试 models.py
文件中编写的代码的逻辑。 特别是,您将测试 Post
模型的 save
方法,以确保它在调用时创建正确的帖子标题段。
让我们首先查看 Post
模型的 save
方法的 models.py
文件中已有的代码:
cd ~/my_blog_app/blog/blogsite nano models.py
您将看到以下内容:
~/my_blog_app/blog/blogsite/models.py
class Post(models.Model): ... def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.title) super(Post, self).save(*args, **kwargs) ...
我们可以看到它会检查即将保存的帖子是否有 slug 值,如果没有,则调用 slugify
为其创建一个 slug 值。 这是您可能想要测试的逻辑类型,以确保在保存帖子时实际创建 slug。
关闭文件。
要对此进行测试,请返回 test_models.py
:
nano test_models.py
然后将其更新为以下内容,添加突出显示的部分:
~/my_blog_app/blog/blogsite/tests/test_models.py
from django.test import TestCase from django.template.defaultfilters import slugify from blogsite.models import Post class ModelsTestCase(TestCase): def test_post_has_slug(self): """Posts are given slugs correctly when saving""" post = Post.objects.create(title="My first post") post.author = "John Doe" post.save() self.assertEqual(post.slug, slugify(post.title))
这种新方法 test_post_has_slug
创建一个标题为 "My first post"
的新帖子,然后为帖子指定作者并保存帖子。 之后,使用 Python unittest 模块中的 assertEqual
方法,它检查帖子的 slug 是否正确。 assertEqual
方法检查传递给它的两个参数是否由 "=="
运算符确定,如果不相等则引发错误。
保存并退出test_models.py
。
这是可以测试的示例。 添加到项目中的逻辑越多,需要测试的内容就越多。 如果您向 save
方法添加更多逻辑或为 Post
模型创建新方法,则需要在此处添加更多测试。 您可以将它们添加到 test_post_has_slug
方法或创建新的测试方法,但它们的名称必须以 test
开头。
您已成功为 Post
模型创建了一个测试用例,您在该模型中断言保存后正确创建了 slug。 在下一步中,您将编写一个测试用例来测试视图。
第三步——使用 Django 的测试客户端
在这一步中,您将编写一个使用 Django 测试客户端测试视图的测试用例。 测试客户端 是一个 Python 类,它充当虚拟 Web 浏览器,允许您测试视图并以与用户相同的方式与 Django 应用程序交互。 您可以通过参考您的测试方法中的self.client
来访问测试客户端。 例如,让我们在 test_views.py
中创建一个测试用例。 首先,打开test_views.py
文件:
nano test_views.py
然后添加以下内容:
~/my_blog_app/blog/blogsite/tests/test_views.py
from django.test import TestCase class ViewsTestCase(TestCase): def test_index_loads_properly(self): """The index page loads properly""" response = self.client.get('your_server_ip:8000') 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
文件):
cd ~/my_blog_app/blog
然后运行它们:
python manage.py test
您将在终端中看到类似于以下内容的输出:
OutputCreating test database for alias 'default'... System check identified no issues (0 silenced). .. ---------------------------------------------------------------------- Ran 2 tests in 0.007s OK Destroying test database for alias 'default'...
在这个输出中,有两个点 ..
,每个点代表一个通过的测试用例。 现在您将修改 test_views.py
以触发失败的测试。 首先打开文件:
nano test_views.py
然后将突出显示的代码更改为:
~/my_blog_app/blog/blogsite/tests/test_views.py
from django.test import TestCase class ViewsTestCase(TestCase): def test_index_loads_properly(self): """The index page loads properly""" response = self.client.get('your_server_ip:8000') self.assertEqual(response.status_code, 404)
此处您已将状态代码从 200
更改为 404
。 现在使用 manage.py
从您的目录再次运行测试:
python manage.py test
您将看到以下输出:
OutputCreating test database for alias 'default'... System check identified no issues (0 silenced). .F ====================================================================== FAIL: test_index_loads_properly (blogsite.tests.test_views.ViewsTestCase) The index page loads properly ---------------------------------------------------------------------- Traceback (most recent call last): File "~/my_blog_app/blog/blogsite/tests/test_views.py", line 8, in test_index_loads_properly self.assertEqual(response.status_code, 404) AssertionError: 200 != 404 ---------------------------------------------------------------------- Ran 2 tests in 0.007s FAILED (failures=1) Destroying test database for alias 'default'...
您会看到一条描述性的失败消息,告诉您失败的脚本、测试用例和方法。 它还会告诉您失败的原因,在这种情况下状态码不等于 404
,并显示消息 AssertionError: 200 != 404
。 此处的 AssertionError
在 test_views.py
文件中突出显示的代码行中引发:
~/my_blog_app/blog/blogsite/tests/test_views.py
from django.test import TestCase class ViewsTestCase(TestCase): def test_index_loads_properly(self): """The index page loads properly""" response = self.client.get('your_server_ip:8000') self.assertEqual(response.status_code, 404)
它告诉您断言为假,即响应状态代码 (200
) 不是预期的 (404
)。 在失败消息之前,您可以看到两个点 ..
现在已更改为 .F
,这告诉您第一个测试用例通过了,而第二个没有。
结论
在本教程中,您在 Django 项目中创建了一个测试套件,向测试模型和视图逻辑添加了测试用例,学习了如何运行测试,并分析了测试输出。 下一步,您可以为不在 models.py
和 views.py
中的 Python 代码创建新的测试脚本。
以下是一些在使用 Django 构建和测试网站时可能会有所帮助的文章:
- Django 单元测试 文档
- Scaling Django系列教程
您还可以查看我们的 Django 主题页面 以获取更多教程和项目。