原创 智慧rdda大成 AI开发测试实战 1周前

Django 实战4

Django自动化测试

什么是自动化测试?

测试是检查代码操作的例程。是软件工程中的重要一个流程;
测试在不同的层次上进行。有些测试可能会应用于一个微小的细节(特定的模型方法是否按预期返回值?)其他人则检查软件的整体操作(站点上的一系列用户输入是否产生了预期的结果?)。这与之前所做的测试没有什么不同,使用shell检查方法的行为,或者运行应用程序并输入数据检查方法的行为。
自动化测试的不同之处在于,测试工作是由系统为您完成的。只需创建一组测试,然后在对应用程序进行更改时,就可以检查代码是否仍按最初的预期工作,而无需执行耗时的手动测试。
为什么需要创建测试?
在学习编写代码的时候,你可能会觉得,仅仅学习Python/Django就已经足够了,而且还有另一件事情要学习和做,这似乎是压倒性的,也许是不必要的。毕竟,我们的polls应用程序现在工作得非常愉快;经历创建自动测试的麻烦并不能使它工作得更好。如果创建polls应用程序是Django编程的最后一步,那么如果是这样的话,您不需要知道如何创建自动测试。但是,如果不是这样的话,现在是学习的好时机。

测试将节省您的时间

在一定程度上,“检查它是否工作”将是一个令人满意的测试。在更复杂的应用程序中,组件之间可能有许多复杂的交互。
这些组件中的任何一个的更改都可能对应用程序的行为产生意想不到的后果。检查它是否仍然“似乎有效”可能意味着用20种不同的测试数据来运行代码的功能,以确保没有破坏某些东西—这不是很好地利用时间。当自动测试可以在几秒钟内完成这项工作时,这一点尤其正确。如果出现了问题,测试还将帮助识别导致意外行为的代码。有时,为了面对编写测试这项乏味而乏味的工作而脱离富有成效的、创造性的编程工作似乎是件烦人的事,尤其是当您知道自己的代码工作正常时。然而,编写测试的任务要比花几个小时手动测试应用程序或试图找出新引入的问题的原因更有成就感。

基本测试策略

测试有很多方法,TDD,手工测试,功能测试,接口测试,单元测试,集成测试等等,那么我们可能需要按照某种连路,或者说是策略来进行。

有些程序员遵循一种称为“测试驱动开发”的规程;他们实际上在编写代码之前编写测试。这似乎有悖常理,但事实上,这与大多数人通常会做的事情相似:他们描述一个问题,然后创建一些代码来解决它。测试驱动开发在Python测试用例中形式化了问题。更多的时候,测试新手会创建一些代码,然后决定应该进行一些测试。也许早一点编写一些测试会更好,但现在就开始永远不会太迟。

有时很难弄清楚从哪里开始写测试。如果您已经编写了几千行Python,那么选择要测试的内容可能并不容易。在这种情况下,在下次进行更改时编写第一个测试是很有成效的,无论是添加新特性还是修复bug。

我们发现了一个BUG

在mysite 目录下,输入下面命令
python manage.py shell

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

True

可以看出,查询语句中,因为未来的事情不是“最近的”,这显然是错误的。

那么我就开始编写一个python测试类tests.py

from django.test import TestCaseimport datetimefrom django.test import TestCasefrom django.utils import timezonefrom .models import Questionclass 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)

这里我们创建了一个django.test.TestCase测试用例使用一个方法创建一个问题实例,该实例的发布日期为将来。然后我们检查was_published_recently()的输出-应该是False

运行测试类

python manage.py test pollsCreating test database for alias 'default'...System check identified no issues (0 silenced).F======================================================================FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests)was_published_recently() returns False for questions whose pub_date----------------------------------------------------------------------Traceback (most recent call last):  File "D:\Users\dev\PycharmProjects\pythonProject\mysite\polls\tests.py", line 19, 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.001sFAILED (failures=1)Destroying test database for alias 'default'...

F==Fail 失败1条记录

分析代码

manage.py test polls在polls应用程序中查找测试
它发现了django.test.TestCase测试用例,它创建了一个专门的数据库用于测试,它寻找测试方法——名称以test开头的方法,在测试中,它创建了一个问题实例,其发布日期字段为未来30天,通过断言assertIs()方法,它发现它的was_published_recently()返回True,但我们希望它返回False
测试告诉我们哪个测试失败了,甚至是失败发生的那一行。

Fix Bug

发现了bug要修复一下,代码中需要增加一个判断
():    now  timezonenow()
那么再次执行python manage.py test polls
Creating test database for alias 'default'...System check identified no issues (0 silenced)..----------------------------------------------------------------------Ran 1 test in 0.001sOKDestroying test database for alias 'default'...
更全面的测试

更全面的测试,意味着更多的测试用例需要编写,测试用例编写需要根据实际的需求进行编写。我们在tests.py 增加2个testcase,在我们有三个测试证实了这一点最近出版了吗()返回过去、最近和将来问题的合理值。

同样,polls是一个最小的应用程序,但是不管它将来会变得多么复杂,不管它与其他代码交互,我们现在都可以保证我们为之编写测试的方法将以预期的方式运行
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)

测试视图

Django提供了一个测试客户机来模拟用户在视图级别与代码交互。我们可以用在tests.py shell,从shell开始,在shell中我们需要做一些tests.py. 首先是在shell中设置测试环境:

setup_test_environment()安装一个模板呈现程序,它允许我们检查响应中的一些附加属性,例如响应.上下文否则就不可能了。请注意,此方法不会设置测试数据库,因此将对现有数据库运行以下操作,并且根据您已经创建的问题,输出可能略有不同。如果你的时区在,你可能会得到意想不到的结果settings.py.py不正确。如果您不记得以前设置过,请在继续之前检查它。

接下来,我们需要导入测试客户机类(稍后在测试.py我们将使用django.test.TestCase测试用例类,它附带了自己的客户机,因此这不是必需的)

python manage.py shellPython 3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:52:53) [MSC v.1927 64 bit (AMD64)] on win32Type "help", "copyright", "credits" or "license" for more information.(InteractiveConsole)>>> from django.test.utils import setup_test_environment>>> setup_test_environment()>>> from django.test import Client>>> client = Client()    #实例化一个client>>> response = client.get("/") #请求/Not Found: />>> response.status_code  #获取https 状态404>>> response = client.get(reverse('polls:index')) Traceback (most recent call last):  File "<console>", line 1, in <module>NameError: name 'reverse' is not defined   ##没有定义>>> from django.url import reverseTraceback (most recent call last):  File "<console>", line 1, in <module>ModuleNotFoundError: No module named 'django.url'  ##没有发现module 单词拼写错误>>> from django.urls import reverse>>> response = client.get(reverse('polls:index'))>>> response.content  ##打印content 内容b'\n    <ul>\n    \n        <li><a href="/polls/6/">what do you do ?</a></li>\n    \n        <li><a href="/polls/5/">\xe4\xbd\xa0\xe7\x9a\x84\xe7\x94\xb5\xe8\x84\x91\xe5\x90\x8d\xe7\xa7\xb0\xef\xbc\x9f</a></li>\n    \n        <li><a href="/polls/4/">\xe4\xbd\xa0\xe7\x9a\x84\xe5\x90\x8d\xe5\xad\x97\xef\xbc\x9f</a></li>\n    \n        <li><ahref="/polls/3/">\xe8\xaf\xb7\xe9\x97\xae\xe4\xbd\xa0\xe7\x9a\x84\xe5\xad\xa6\xe6\xa0\xa1\xe6\x98\xaf\xe5\x93\xaa\xe9\x87\x8c?\xe4\xb8\xba\xe4\xbb\x80\xe4\xb9\x88\xe8\xbf\x99\xe4\xb9\x88\xe9\x97\xae?</a></li>\n    \n        <li><a href="/polls/2/">Who are you?</a></li>\n    \n        <li><a href="/polls/1/">What time is it?</a></li>\n    \n    </ul>\n'>>> response.context['lastst_question_list'] ## 获取最新额latest_question_list Traceback (most recent call last):  File "<console>", line 1, in <module>  File "d:\Users\dev\PycharmProjects\pythonProject\venv\lib\site-packages\django\template\context.py", line 83, in __getitem__    raise KeyError(key)    ##单词拼写错误KeyError: 'lastst_question_list'>>> response.context['latest_question_list'] ##获取list 所有记录 5条<QuerySet [<Question: what do you do ?>, <Question: 你的电脑名称?>, <Question: 你的名字?>, <Question: 请问你的学校是哪里?为什么这么问?>, <Question: Who are you?>, <Question: What time is it?>]>>>>
发现一个问题:还是时间没有进行判断
民意调查列表显示尚未发布的民意调查(即那些在未来有发布日期的民意调查)
return Question.objects.filter(        pub_date__lte=timezone.now()    ).order_by('-pub_date')[:5]

测试新的观点

添加tests.py 代码如下

from django.urls import reversedef 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, "没有问题记录!.")        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, "没有问题记录!.")        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.>']        )
首先测试我们的新视图是一个问题快捷方式函数create_question,用来消除创建问题过程中的一些重复。
test_no_questions不会创建任何问题,但会检查消息:“no polls are available.”并验证latest_question_list是否为空。请注意django.test.TestCase测试用例类提供了一些附加的断言方法。在这些示例中,我们使用assertContains()和assert queryst equal()。
在test_pass_question中,我们创建一个问题并验证它是否出现在列表中。
在test_future_question中,我们创建了一个将来有发布日期的问题。每个测试方法都会重置数据库,因此第一个问题不再存在,因此索引中也不应该有任何问题。
等等。实际上,我们正在使用这些测试来讲述站点上的管理输入和用户体验的故事,并检查是否在每个状态以及系统状态的每个新更改中,都会发布预期的结果。
index.html 页面也要修改一下
<p>没有问题记录!.</p>
测试详情页面

也是时间没有加上判断,全运行python manage.py test polls

def get_queryset(self):        return Question.objects.filter(pub_date__lte=timezone.now())
Creating test database for alias 'default'...System check identified no issues (0 silenced)...........----------------------------------------------------------------------Ran 10 tests in 0.039sOKDestroying test database for alias 'default'...

总结

测试时,越多越好,可以定义聚类的测试用例集合,方便测试代码公用。
似乎我们的测试正在失控。以这种速度,我们的测试中的代码将很快多于我们的应用程序中的代码,与我们其余代码的优雅简洁相比,重复是不美观的。没关系。让他们成长。在大多数情况下,您可以编写一次测试,然后忘记它。当你继续开发你的程序时,它将继续执行它有用的功能。有时需要更新测试。假设我们修正了我们的观点,这样只有带有选择的问题才会被发表。在这种情况下,我们现有的许多测试都将失败——确切地告诉我们哪些测试需要修改以使其更新,因此在某种程度上,测试有助于照顾自己。最坏的情况是,当您继续开发时,您可能会发现有些测试现在是多余的。即使这样也不是问题;在测试中,冗余也是一件好事。只要你的测试安排合理,它们就不会变得难以管理。好的经验法则包括:每个模型或视图都有一个单独的TestClass针对要测试的每一组条件的单独测试方法。

--END--

(0)

相关推荐