在Django中测试
测试简介
像所有成熟的编程语言一样,Django提供内置的单元测试功能。 单元测试是一个软件测试过程,测试软件应用程序的各个单元以确保它们执行所期望的任务。
单元测试可以在多个层次上执行 - 从测试单个方法,看它是否返回正确的值以及如何处理无效数据,直到测试整套方法以确保用户输入序列达到预期结果。
单元测试基于四个基本概念:
-
测试夹具是执行测试所需的设置。这可能包括数据库,示例数据集和服务器设置。测试夹具还可以包括在执行测试后所需的任何清理动作。
-
测试用例是测试的基本单元。测试用例检查给定的一组输入是否会产生一组预期的结果。
-
测试套件是作为一个组执行的大量测试用例或其他测试套件。
-
测试运行器是控制测试执行并将测试结果反馈给用户的软件程序。
软件测试是一个深入细致的主题,本章应该被认为仅仅是单元测试的简单介绍。互联网上有大量关于软件测试理论和方法的资源,我鼓励你对这个重要的主题进行自己的研究。有关Django单元测试方法的更详细讨论,请参阅Django Project网站。
介绍自动测试
什么是自动测试?
在本书中,您一直在测试代码; 也许甚至没有意识到它。 每次使用Django shell查看函数是否工作,或者查看给定输入的输出时,
你正在测试你的代码。 例如,在第2章中,我们将一个字符串传递给一个视图,该视图期望一个整数产生一个TypeError异常。
测试是应用程序开发的一个正常部分,但是自动化测试的不同之处在于测试工作是由系统为您完成的。 您只需创建一组测试,然后在对应用程序进行更改时,可以检查代码是否仍然按照您的初始设计进行工作,而无需执行耗时的手动测试。
那么为什么要创建测试?
如果像本书中那样创建简单的应用程序是您所做的Django编程的最后一部分,那么确实如此,您不需要知道如何创建自动化测试。 但是,如果您希望成为专业程序员和/或从事更复杂的项目,您需要知道如何创建自动化测试。
创建自动化测试将会:
-
节省您的时间。 手动测试大型应用程序组件之间的无数复杂交互是耗时且容易出错的。 自动化测试可以节省时间,让您专注于编程。
-
预防问题。 测试强调了你的代码的内部工作,所以你可以看到问题出在哪里。
-
看起来专业。 专业人员编写测试。 Django最初的开发人员之一Jacob Kaplan-Moss说:“没有测试的代码是被设计破坏的。”
-
改进团队合作。 测试保证同事不会无意中破坏你的代码(并且你不会在不知情的情况下破坏他们的代码)。
基本测试策略
写测试有很多方法。 一些程序员遵循一种称为“测试驱动开发”的规则; 他们在编写代码之前实际编写测试。 这看起来可能与直觉相反,但实际上它与大多数人经常会做的事情类似:他们描述一个问题,然后创建一些代码来解决它。
测试驱动开发只是在Python测试用例中形式化问题。 更多的时候,测试的新手会创建一些代码,然后决定应该进行一些测试。 也许早些时候写一些测试会更好,但开始之前永远不会太晚。
写测试
要创建您的第一个测试,让我们在您的Book模型中引入一个错误。
假设您已决定在您的书籍模型上创建自定义方法,以指示该书是否最近已发布。 您的书籍模型可能如下所示:
import datetime
from django.utils import timezone
from django.db import models
# ... #
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
def recent_publication(self):
return self.publication_date >= timezone.now().date() - datetime.timedelta(we\
eks=8)
# ... #
首先我们导入了两个新模块:Python的日期时间和来自django.utils的时区。 我们需要这些模块来计算日期。 然后,我们在Book模型中添加了一个名为recent_publication的自定义方法,该方法计算出8周前的日期,如果本书的发布日期更近,则返回true。
所以让我们跳转到交互式shell并测试我们的新方法:
python manage.py shell
>>> from books.models import Book
>>> import datetime
>>> from django.utils import timezone
>>> book = Book.objects.get(id=1)
>>> book.title
'Mastering Django: Core'
>>> book.publication_date
datetime.date(2016, 5, 1)
>>>book.publication_date >= timezone.now().date() - datetime.timedelta(weeks=8)
True
到目前为止,我们已经导入了我们的书籍模型并检索了一本书。 今天是2016年6月11日,我已经在5月1日的数据库中输入了我的书的出版日期,这是不到8周前的,所以函数正确返回True。
很明显,您必须修改数据中的发布日期,以便您完成此练习时,此练习仍适用于您。
现在让我们看看如果我们将发布日期设置为未来某个时间(比如说9月1日)会发生什么情况:
>>> book.publication_date
datetime.date(2016, 9, 1)
>>>book.publication_date >= timezone.now().date() - datetime.timedelta(weeks=8)
True
哎呀! 这里显然是错误的。 您应该能够快速地看到逻辑中的错误 - 8周之前的任何日期都将返回true,包括将来的日期。
因此,忽略这是一个颇为人为的例子,现在让我们创建一个测试来揭示我们的错误逻辑。
创建一个测试
当您使用Django的startapp命令创建书籍应用程序时,它会在您的应用程序目录中创建一个名为tests.py的文件。 这是书籍应用程序的任何测试应该去的地方。 所以让我们开始吧,写一个测试:
import datetime
from django.utils import timezone
from django.test import TestCase
from .models import Book
class BookMethodTests(TestCase):
def test_recent_pub(self):
"""
recent_publication() should return False for future publication
dates.
"""
futuredate = timezone.now().date() + datetime.timedelta(days=5)
future_pub = Book(publication_date=futuredate)
self.assertEqual(future_pub.recent_publication(), False)
这应该是非常直接的,因为它几乎和我们在Django shell中所做的一样,唯一真正的区别是我们现在已经将我们的测试代码封装在一个类中,并创建了一个断言,测试我们的recent_publication()方法与未来日期。
我们将在本章后面更详细地介绍测试类和assertEqual方法 - 现在我们只想看看测试如何在非常基本的层面上工作,然后再讨论更复杂的主题。
运行测试
现在我们已经创建了我们的测试,我们需要运行它。 幸运的是,这很容易做到,跳进你的终端并输入:
python manage.py test books
过了一会儿,Django应该打印出如下所示的内容:
Creating test database for alias 'default'...
F
======================================================================
FAIL: test_recent_pub (books.tests.BookMethodTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\Nigel\ ... mysite\books\tests.py", line 25, in test_recent_pub
self.assertEqual(future_pub.recent_publication(), False)
AssertionError: True != False
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
Destroying test database for alias 'default'...
在这个例子中发生了什么:
-
python manage.py test book在书籍应用程序中查找测试
-
它找到了django.test.TestCase类的一个子类
-
它创建了一个专门用于测试目的的数据库
-
它寻找名称以“test”开头的方法
-
在test_recent_pub中,它创建了一个Book实例,其发布日期字段将来是5天; 和
-
使用assertEqual()方法,它发现它的recent_publication()返回True,当它应该返回False
-
测试通知我们哪个测试失败,甚至发生故障的线路。 另请注意,如果您在* nix系统或Mac上,则文件路径将有所不同。
这就是在Django中进行测试的基本介绍。 正如我在本章开头所说的那样,测试是一个深刻且详细的主题,对于您作为程序员的职业生涯来说非常重要。 我不可能在一章中涵盖测试的所有方面,所以我鼓励您深入研究本章中提及的一些资源以及Django文档。
在本章剩余部分中,我将介绍Django提供的各种测试工具。