【Django第一步】第五部分:测试
@[Django| 翻译|python]
本教程将为我们创建一些自动化测试
什么是自动化测试
测试是检验代码的最简单的途径
在不同的层面测试程序,有些测试适用于微小的部分(一个模型的方法是否返回了预期的值?),而另一些则从整体上测试程序(网站的用户输入是否返回了预期的结果?),这与教程2中的测试并无不同,使用shell检测程序,或者运行整体程序来输入数据检测它的反应
区别在于自动化测试的测试工作是由机器完成的。你创建一组测试数据,然后改自己的代码时,可以检测代码是否如你预期的一样工作,而不需要手动输入测试。
为什么需要创建测试
所以怎么创建测试呢?以及为什么 现在就要创建测试呢?
你可能觉得自己学的Python/Django已经足够了,并且自己有其他的东西需要学习,觉得学习这是无关紧要的。毕竟,我们的polls程序现在还运行的很好。花费时间创建测试并不会使它工作的更好。如果创建测试是polls app的最后一步,那么确实你不需要去做,但是如果不是,那么现在是学习自动化测试的最好时机。
自动化测试会节约你的时间
在某些时候,“测试它是否正常工作”是一个非常让人满意的举动。在某些复杂的程序中,你可能会有十几个组件在进行交互。
任何一个组件的非期望反应都会影响整体的一系列反应。检测它是否正常工作意味着你需要自己输入二十几组乃至更多的数据去查看它是否正常工作,这对你的时间不是一个非常好的利用。
而自动化测试可以在几秒内完成测试,并且可以给出出错的提示。
有时,将自己从生产性的,富有创造性的编程工作中解放出来,面对写作测试这些毫无吸引力和令人沮丧的事情,特别是当你知道你的代码工作正常时,这让人觉得非常的麻烦。
然而,编写测试的任务要比花费几个小时手动测试应用程序或试图确定新引入问题的原因更有成就感。
测试不只是发现问题,还可以阻止它们
没有测试,应用程序的目的或预期行为可能会相当不透明。即使它是你自己的代码,你有时会发现自己在试图弄清它到底在做什么。
测试改变了这一点; 他们从内部点亮你的代码,当出现问题时,他们会把注意力集中在出错的部分 - 即使你甚至没有意识到出错了。
测试使你的代码更加吸引人
您可能已经创建了一个非常好的软件,但是你会发现很多开发者会因为它缺少测试而不想去看它。如果没有测试,他们不会去相信它。。Django最初的开发人员之一Jacob Kaplan-Moss说:“没有测试的代码是设计的失误”
其他开发人员希望在认真对待软件之前查看软件中的测试是您开始编写测试的另一个原因。
测试使团队工作更好
之前写的观点都是对于维持一个程序的独立开发者而言的,但是对于复杂的应用程序,维护它的往往是一个团队。
测试保证了你的同事不会无意中破坏你的代码(因为他们不会在自己不明白的时候去修改代码),如果你要成为一个好的Django开发者,就必需要擅长写测试
基本的测试策略
有很多方法来写你的测试
一些程序员遵循一种称为“ 测试驱动开发 ”的规则; 他们在编写代码之前实际编写测试。这看起来可能与直觉相反,但实际上它与大多数人经常会做的事情类似:他们描述一个问题,然后创建一些代码来解决它。测试驱动开发只是在Python测试用例中形式化问题。
更多的时候,测试的新手会创建一些代码,然后决定应该进行一些测试。也许早些时候写一些测试会更好,但开始之前永远不会太晚。
有时候很难确定从哪里开始编写测试。如果您已经编写了几千行Python,则选择要测试的内容可能并不容易。在这种情况下,在您下次进行更改时编写第一个测试,无论是添加新功能还是修复错误,都是富有成效的。
编写我们的第一个测试
确定一个bug
幸运的是,在我们的polls程序中已经预留了一个小的bug需要我们去修复。在Question的Question_was_published_recently()
方法中,如果测试日期是未来的某一天,由于它和发布时间相差同样在一天以内,所以就会返回true
为了检验这个问题真的存在,我们在python shell中新建一个future question来测试
>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> # create a Question instance with pub_date 30 days in the future
>>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
>>> # was it published recently?
>>> future_question.was_published_recently()
True
由于未来的事情不是“最近的”,这显然是错误的
创建一个测试来确定问题
将以下代码写入polls/tests.py
import datetime
from django.utils import timezone
from django.test import TestCase
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is in the future.
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
我们在这里完成的工作是创建一个django.test.TestCase子类,其中有一个方法可以在未来创建一个Question实例pub_date。然后我们检查输出was_published_recently()
运行测试
在终端中我们运行我们的测试
python manage.py test polls
输出:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question
self.assertIs(future_question.was_published_recently(), False)
AssertionError: True is not False
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Destroying test database for alias 'default'...
在过程中发生了以下事件
- python manage.py test polls
在polls应用程序中寻找测试
- 它找到了这个类的一个子django.test.TestCase类
- 它创建了一个专门用于测试目的的数据库
- 它寻找测试方法 - 名称以开头的名称 test
- 在test_was_published_recently_with_future_question
创建了一个Question 实例,其pub_date将来30天
…并使用该assertIs()方法,它发现它的 was_published_recently()
是True,尽管我们希望它回来 False
测试通知我们哪个测试失败,甚至发生故障的线路。
修复
我们已经知道bug是什么,如果Question的pub_date在未来,那么Question.was_published_recently()
应该返回false。所以我们需要修改方法
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
现在在运行测试程序:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Destroying test database for alias 'default'...
在确定了一个错误之后,我们编写了一个测试,公开它并纠正了代码中的错误,以便我们的测试通过。
未来我们的应用程序可能会出现其他许多问题,但我们可以肯定,我们不会无意中重新引入此错误,因为只需运行测试就会立即发出警告。我们可以认为应用程序的这一小部分永远安全地固定下来。
更全面的测试
当我们在这里时,我们可以进一步确定was_published_recently()
方法; 事实上,如果在修复一个bug的时候我们同时也引入了另一个的bug,这会让人感到非常尴尬。
我们可以将两个乃至更多的测试添加到同一个类中,以便更全面的测试程序
def test_was_published_recently_with_old_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is older than 1 day.
"""
time = timezone.now() - datetime.timedelta(days=1, seconds=1)
old_question = Question(pub_date=time)
self.assertIs(old_question.was_published_recently(), False)
def test_was_published_recently_with_recent_question(self):
"""
was_published_recently() returns True for questions whose pub_date
is within the last day.
"""
time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59)
recent_question = Question(pub_date=time)
self.assertIs(recent_question.was_published_recently(), True)
测试视图
民意调查的应用程序是不加区别的:它会公布任何问题,包括pub_date未来领域的问题。我们应该改善这一点。pub_date在未来设置应该意味着该问题在当时公布,但在那之前是不可见的。
视图测试
当我们修复上面的错误时,我们先编写了测试,然后再编写代码来修复它,事实上,这是一个简单的测试驱动开发的例子,但是我们的工作顺序并不重要。
在我们的第一个测试中,我们密切的关注了代码的内部行为。对于这个测试,我们想要检查它的行为,就像用户通过web浏览器所经历的那样。
在我们尝试解决问题之前,让我们看看我们可以使用的工具
Django的测试客户端
Django提供了一个测试Client来模拟用户在视图层面上和代码的交互。