测试很重要,但又不重要。对于初学者,不应该在测试上花费太多的精力,而对于高级程序员,测试属于必须具备的能力。本教程针对初学者,因此基本不介绍测试相关的内容。
什么是自动化测试
测试是一种例行的、不可缺失的工作,用于检查你的程序是否符合预期。
测试可以划分为不同的级别。一些测试可能专注于小细节(比如某一个模型的方法是否会返回预期的值?), 一些测试则专注于检查软件的整体运行是否正常(用户在对网站进行了一系列的输入后,是否返回了期望的结果?)。
测试可以分为手动测试和自动测试。手动测试很常见,有时候print一个变量内容,都可以看做是测试的一部分。手动测试往往很零碎、不成体系、不够完整、耗时费力、效率低下,测试结果也不一定准确。
自动化测试则是系统地较为完整地对程序进行测试,效率高,准确性高,并且大部分共同的测试工作会由系统来帮你完成。一旦你创建了一组自动化测试程序,当你修改了你的应用,你就可以用这组测试程序来检查你的代码是否仍然同预期的那样运行,而无需执行耗时的手动测试。
为什么需要测试?
大家都明白:
Django是一个全面、完善、严谨的Web框架,当然不会缺少测试功能。
很巧,在我们的投票应用中有一个小bug需要修改:在Question.was_published_recently()
方法的返回值中,当Qeustion在最近的一天发布的时候返回True(这是正确的),然而当Question在未来的日期内发布的时候也返回True(这是错误的)。
我们可以在admin后台创建一个发布日期在未来的Question,然后在shell中验证这个bug:
$ python manage.py shell
>>> import datetime >>> from django.utils import timezone >>> from polls.models import Question >>> # 创建一个发布日期在30天后的问卷 >>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30)) >>> # 测试一下返回值 >>> future_question.was_published_recently() True
问题的核心在于我们允许创建在未来时间才发布的问卷,由于“未来”不等于“最近”,因此这显然是个bug。
刚才我们是在shell中测试了这个bug,那如何通过自动化测试来发现这个bug呢?
通常,我们会把测试代码放在应用的tests.py
文件中,测试系统将自动地从任何名字以test开头的文件中查找测试程序。每个app在创建的时候,都会自动创建一个tests.py
文件,就像views.py
等文件一样。
将下面的代码输入投票应用的polls/tests.py
文件中:
import datetime from django.utils import timezone from django.test import TestCase from .models import Question class QuestionMethodTests(TestCase): def test_was_published_recently_with_future_question(self): """ 在将来发布的问卷应该返回False """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) self.assertIs(future_question.was_published_recently(), False)
我们在这里创建了一个django.test.TestCase
的子类,它具有一个方法,该方法创建一个pub_date
在未来的Question实例。最后我们检查was_published_recently()
的输出,它应该是 False。
在终端中,运行下面的命令,
$ 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
命令会查找投票应用中所有的测试程序django.test.TestCase
的子类test
开头的测试方法test_was_published_recently_with_future_question
方法中,创建一个Question实例,该实例的pub_data字段的值是30天后的未来日期。assertIs()
方法,它发现was_published_recently()
返回了True,而不是我们希望的False。最后,测试程序会通知我们哪个测试失败了,错误出现在哪一行。
整个测试用例基本上和Python内置的unittest
非常相似,大家可以参考Python教程中测试相关的章节。
我们已经知道了问题所在,现在可以去修复bug了。修改源代码,具体如下:
# polls/models.py 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'...
可以看到bug已经没有了。
事实上,前面的测试用例还不够完整,为了使was_published_recently()
方法更加可靠,我们在上面的测试类中再额外添加两个其它的方法,来更加全面地进行测试。
# polls/tests.py def test_was_published_recently_with_old_question(self): """ 只要是超过1天的问卷,返回False """ 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): """ 最近一天内的问卷,返回True """ 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)
现在我们有三个测试来保证无论发布时间是在过去、现在还是未来Question.was_published_recently()
都将返回正确的结果。
关于测试的内容,虽然重要,但是对于刚入门者而言却不是最首要的知识点,等将主体内容掌握后,我们再回头来梳理这一部分。
有点迷糊了! 在修复环节: def was_published_recently(self): now = timezone.now() return now - datetime.timedelta(days=1) <= self.pub_date <= now 这段代码是添加到那里? 是添加到polls/models.py中吗? 如果添加到polls/models.py中,那应该添加在class Question(models.Model): 里面吗?
问题解决了 原来 我引用的方法不对
django.db.utils.ProgrammingError: (1146, "Table 'test_test.TestModel_contact' doesn't exist") 我在运行python manage.py test polls后出现这个错误,请问各位有什么可以给出一点解决参考吗
我登录mysql数据库,查看了test_test数据库中的表,里头有 | TestModel_test | | auth_group | | auth_group_permissions | | auth_permission | | auth_user | | auth_user_groups | | auth_user_user_permissions | | django_admin_log | | django_content_type | | django_migrations | | django_session | | polls_choice | | polls_question 就是少了那个TestModel_contact
ERRORS: polls.Choice.question: (fields.E300) Field defines a relation with model 'Question', which is either not installed, or is abstract. 测试代码加上后就一直包这个错误?不知道为啥?我要怎么解决?没加之前一直都是正常的。
已解决
为什么会vote() got an unexpected keyword argument 'question'啊 明明都一步步跟着来的 到polls/1/vote/就会报错 传值好像传不进去....
为什么我在遇见BUG那一部分就报错了,弄了半天都是这个样子。下面是cmd上的报错信息,楼主你能帮我看看吗 TypeError: Question() got an unexpected keyword argument 'pub_date
提示说获得了一个不需要的参数。建议检查一下Question的定义中关于pub_data的部分。
博主你好,有个小问题,举例的测试是先知道了这方面有问题,才按这个问题增加的测试条件,如果软件在开发的时候就知道问题了,那肯定已经做了解决方案,所以,怎么才能去测试未知的问题呢?
测试工作有经验可循、有历史可查、有套路可用,极限测试、边界测试、压力测试等等。这些都是通用的,或者可以事先预知或考虑到的。 对于那些未知的问题,不可预见的问题当然都是出现了一个解决一个。如果所有问题都能被预知,那就不存在bug,也没有维护、维修、升级的事了,这是不可能的。
在测试类中自定义的函数中可以看到只有函数名称不一样,timedelta的参数类型也没有区别,其他代码都一样,程序是如何区分的时态?仅仅根据函数名就可以了吗?如果仅仅根据函数名,那是不是命名也有必须遵循的规则?
这里区分的是时间点位置,而不是英语语法上的时态。函数名取得好能帮助程序员理解代码,并不影响执行过程。
time = timezone.now() - datetime.timedelta(days=1, minutes=1) #一天时除天数外还需要加时间可选参数,否则报错,请问为什么?
测试 果然是最被嫌弃的一部分吗? 哈哈
哈哈哈, 测试很重要的.
The test is very important.