“Code without tests is broken by design.”

时间:2021-08-16 00:50:33

​https://docs.djangoproject.com/en/4.1/intro/tutorial05/​

it’s never too late to get started.

今天主要接上一章节~从testing这篇官方文档开始看起。

“Code without tests is broken by design.”

这一个测试讲述了很多至理名言,包括标题和第一句。
而第一个测试从这里开始:

class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __str__(self):
return self.question_text
def was_published_recently(self):
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

这个Question类是前几章节建立的,而was_published_recently则是问是否是最近发布的。
最后这个

self.pub_date >= timezone.now() - datetime.timedelta(days=1)

则是判断返回True和False的判定。
这一句的判定是当前的发布时间是否大于或等于 当前时间减去一天。
也就是起码是昨天的新发布数据才是recently发布的。

>>> import datetime
>>> from django.utils import timezone
>>> from polls.models import Question
>>> future_question = Question(pub_date=timezone.now()+datetime.timedelta(days=30))
>>> future_question.was_published_recently()

Now, do the testing.
通过python manage.py shell进行shell命令模式执行以上命令。
而这里的timedelta被+了30天,也就是创建了一个pub_date时间是未来30天的一个
future_question,而执行was_published_recently()如果没有问题则
肯定会返回True。

A conventional place for an application’s tests is in the application’s tests.py file; the testing system will automatically find tests in any file whose name begins with test.

这里命令也是很关键,比如以上说明建立test开头的py文件,系统会自动找到并进行测试。

import datetime
from time import timezone

from django.test import TestCase

from polls.models import Question


# Create your tests here.

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)

注意:本类记录中python命令都是以MacOs为准。

python manage.py test polls
Found 1 test(s).
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 "/Users/x/PycharmProjects/djangoProject1/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'...

官方教程中这里就是判定是否是False,而实际上我感觉测试
是应该判定Ture才对的,也许这里只是演示测试失败的结果。

… and using the assertIs() method, it discovered that its was_published_recently() returns True, though we wanted it to return False

实际上官方教程说明,was_published_rencetly()这个看似DDD的方法是有逻辑缺陷的。
应该改为:

def was_published_recently(self):
# return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now

这里的判定则改为了

当前时间 - 1天时间 <= 发布时间 <= 当前时间

也就是发布时间必须是在发布之后1天时间(包括)之内的时间,并且加强判定为
发布时间不能超过当前时间,那样会有逻辑问题。

fix这个问题之后再测 assert False

Found 1 test(s).
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'...

番外篇:
The Django test client
这里我通过这篇test client测试了跟着官方做的项目,
是可以的:

>>> 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'
>>> response.context['latest_question_list']
<QuerySet [<Question: What's up?>]>

那么测试下t项目如何:
答案也是可以的,只不过t项目的shell经过定制化一样,
每打一行都是会有个自增的序列出现。

Improving our view

class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
# Return the last five published questions.
# return Question.objects.order_by('-pub_date')[:5]
return Question.objects.filter(pub_date__lte=timezone.now()).order_by('-pub_date')[:5]

更改view层,像是springboot的controller层,
这里在Django中有很多关键字,比如template_name,context_object_name,
queryset,serializer_class,lookup_field,lookup_url_kwarg,filter_backends,
pagination_class.
具体可以看到rest_framework-stubs/generics.pyi这个源码文件。
而rest framework就是django框架提供的:

r"""
______ _____ _____ _____ __
| ___ \ ___/ ___|_ _| / _| | |
| |_/ / |__ \ `--. | | | |_ _ __ __ _ _ __ ___ _____ _____ _ __| |__
| /| __| `--. \ | | | _| '__/ _` | '_ ` _ \ / _ \ \ /\ / / _ \| '__| |/ /
| |\ \| |___/\__/ / | | | | | | | (_| | | | | | | __/\ V V / (_) | | | <
\_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_|
"""

import django

__title__ = 'Django REST framework'
__version__ = '3.12.4'
__author__ = 'Tom Christie'
__license__ = 'BSD 3-Clause'
__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd'

“Code without tests is broken by design.”

休息一下,马上回来!

通过官方教程的引导,现在polls/tests.py是这样的

import datetime
from django.utils import timezone

from django.test import TestCase

from polls.models import Question
from django.urls import reverse


# Create your tests here.

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_published_recently_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)

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_questions(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):
question = create_question(question_text="Past question.",days=-30)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(response.context['latest_question_list'],[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):
question = 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],)

def test_two_past_questions(self):
question1 = create_question(question_text="Past question 1.",days=-30)
question2 = create_question(question_text="Past question 2.",days=-5)
response = self.client.get(reverse('polls:index'))
self.assertQuerysetEqual(response.context['latest_question_list'],[question2,question1],)

class QuestionDetailViewTests(TestCase):
def test_future_question(self):
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):
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)

再次执行

python manage.py test polls

可以看到执行测试结果OK

Found 10 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
........
----------------------------------------------------------------------
Ran 10 tests in 0.042s

OK
Destroying test database for alias 'default'...

这里官方文档提到Selenium这个框架,虽然之前接触过一点

​​javascript:void(0)​

但没想到其实这个框架支持多种语言:

“Code without tests is broken by design.”


但从Django官方的角度来说,他们更加匹配:

LiveServerTestCase does basically the same as TransactionTestCase with one extra feature: it launches a live Django server in the background on setup, and shuts it down on teardown. This allows the use of automated test clients other than the Django dummy client such as, for example, the Selenium client, to execute a series of functional tests inside a browser and simulate a real user’s actions.

作者:​​ukyo--3点买菜​​