Django学习5
在平时测试代码的效用的时候通常会造成一系列的麻烦,消耗大量的时间,所以jiango提供了一个automated test。这些test可以发现错误的位置,它可以让代码更加通透,更易阅读。
step1:第一个test
在例子中的这个项目是存在bug的如Question这个model的"was_published_recently"这个function中如果创建时间大于当前时间时,也会返回True,不过很明显,这个情况下的pub_date是非法数据。
我们可以打开shell然后插入一个数据来测试一下。
In [1]: import datetime
In [2]: from django.utils import timezone
In [3]: from polls.models import Question
In [4]: future_question = Question(pub_date=timezone.now()+datetime.timedelta(days
...: =30))
In [5]: future_question.was_published_recently()
Out[5]: True
可以看到这里发现了错误,但是这样来查找错误非常不方便,Django就为我们提供了automated test来完成这个步骤,testing system会自动找到那些以test开头的文件来测试。我们在文件结构下会发现一个polls/tests.py的文件,这就是Django默认生成的测试文件,在其中我们增加如下代码:
import datetime
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
然后我们在终端中输入:
py manage.py test shell
会出现“True is not False”的错误,这里“assertIs”就是回去运行"was_published_recently"这个函数放回"Ture"但是我们期望的是“False”。
现在我们去修正一下这个方法然后再去运行一遍test,修正代码如下:
return timezone.now() >= self.pub_date >= timezone.now()-datetime.timedelta(days=1)
发现test返回结果是“OK”
step2:更复杂的test
在完成了上述的test,我们可以预见到,当一个bug修复后引起另一个
bug会发生什么,当一个melthod有数个地方需要test会怎样,那么同样用上面那个function来做例子:
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
def test_was_published_recently_with_old_question(self):
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_rececntly_with_recent_question(self):
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)
这样就完整的测试了recent, old, future。
step3:view的test
在对model的fuction有一个test后,我们发现我们的view也有问题,即是它会将所有的quesiton都进行显示,而不能进行筛选,有些question在设置的时候,时间会设置在未来以便表示是未来那个时间点再上线的question,现在是不可见的状态,但是我们的view会显示这样的question。
在view的层面上,Django提供了一种test client来模拟用户的交互。下面我们可以使用shell来使用一下这个client,首先需要简历环境:
In [1]: from django.test.utils import setup_test_environment
In [2]: setup_test_environment()
这里“setup_test_environment”就是会加载一个template的渲染器,它使得我们可以测试response的一些属性如"response.context"。注意的是它不会创建测试数据库,而是根据你的数据库来测试。如果Time_Zone没有设置正确还会导致一些奇怪的输出。
接下来输入如下的代码:(教程中的代码)
>>> # get a response from '/'
>>> response = client.get('/')
Not Found: /
>>> # we should expect a 404 from that address; if you instead see an
>>> # "Invalid HTTP_HOST header" error and a 400 response, you probably
>>> # omitted the setup_test_environment() call described earlier.
>>> response.status_code
404
>>> # on the other hand we should expect to find something at '/polls/'
>>> # we'll use 'reverse()' rather than a hardcoded URL
>>> from django.urls import reverse
>>> response = client.get(reverse('polls:index'))
>>> response.status_code
200
>>> response.content
b'\n <ul>\n \n <li><a href="/polls/1/">What's up?</a></li>\n \n </ul>\n\n'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>
step4:改进view
在polls/views.py中将get_queryset改为如下:
def get_queryset(self):
return Question.objects.filter(
pub_date__lte=timezone.now()).order_by('-pub_date')[:5]
这里的这个".filter(pub_date__lte=timezone.now())代表pub_date小于等于timezone.now()的数据。
step5:测试新view
这时候就修复了上述的bug,接下来就用test.py来测试一下。在test增加如下的class:
def create_question(question_text, days):
time = timezone.now() + datetime.timedelta(days=days)
return Question.objects.create(question_text=question_text, pub_date=time)
class QuestionIndexViewTests(TestCase):
def test_no_question(self):
response = self.client.get(reverse('polls:index'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_past_question(self):
create_question(question_text="Past question.", days=-30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(response.context['latest_question_list'],
['<Question: Past question.>'])
def test_future_question(self):
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertContains(response, "No polls are available.")
self.assertQuerysetEqual(response.context['latest_question_list'], [])
def test_future_question_and_past_question(self):
create_question(question_text="Past question.", days=-30)
create_question(question_text="Future question.", days=30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question.>']
)
def test_two_past_questions(self):
create_question(question_text="Past question 1.", days=-30)
create_question(question_text="Past question 2.", days=-5)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(
response.context['latest_question_list'],
['<Question: Past question 2.>', '<Question: Past question 1.>']
)
在这其中需要解释的是“assertContains()”:代表response中包含的text;“assertQuerysetEqual()”:代表response中含有的list。
step6:测试detail的view
除了index的view需要改变,detailview也需要改变,因为用户可能会通过猜测url来进入detail页面。所以将detail的view修改为:
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
def get_queryset(self):
return Question.objects.filter(
pub_date__lte=timezone.now()
)
在test.py中加入:
class QuestionDetailViewTests(TestCase):
def test_future_question(self):
"""
The detail view of a question with a pub_date in the future
returns a 404 not found.
"""
future_question = create_question(question_text='Future question.', days=5)
url = reverse('polls:detail', args=(future_question.id,))
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_past_question(self):
"""
The detail view of a question with a pub_date in the past
displays the question's text.
"""
past_question = create_question(question_text='Past Question.', days=-5)
url = reverse('polls:detail', args=(past_question.id,))
response = self.client.get(url)
self.assertContains(response, past_question.question_text)