测试Web应用程序是一项复杂的任务,因为Web应用程序由多层逻辑组成 :从HTTP级请求处理到表单验证和处理,再到模板渲染。使用Django的测试执行框架和各种实用程序,您可以模拟请求,插入测试数据,检查应用程序的输出,并通常验证您的代码正在执行它应该做的事情。
在Django中编写测试的首选方法是使用Python标准库中内置的unittest
模块。您还可以使用任何其他Python测试框架; Django为这种集成提供了API和工具。
Django的单元测试使用Python标准库模块:unittest
。 该模块使用基于类的方法定义测试。在tests.py
文件中编写测试文件:
st import TestCase
from dels import Topic, Post, User
import time
# Create your tests here.class TopicTest(TestCase):def setUp(self):self.user = ate_user(username='TestUser', email='test@qq', password='password')ate(owner=self.user, text='topic test 1')ate(owner=self.user, text='topic test 2')def test_topic_user(self):topic_1 = (text='topic test 1')topic_2 = (text='topic test 2')self.assertEqual(topic_1.owner, self.user)self.assertEqual(topic_2.owner, self.user)
Django在执行setUp()
部分操作时,并不会真正向数据库表中插入数据。所以不用关心testDown()
清理工作。
使用python manage.py test
运行测试:
(venv) ulysses@ulysses:~/PycharmProjects/django_ulysses$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.364sOK
Destroying test database for alias 'default'...
Django会使用内置的测试用例检测装置来检测基于unittest的测试用例。默认情况下,这将在当前工作目录下的任何名为test*.py
的文件中发现测试。从输出结果看,当运行测试时会创建一个测试用的数据库,之后Django会运行这些测试。如果测试通过就会出现上面显示的ok
信息,一旦测试出现问题就会提示详细的错误信息,如:
FAIL: test_was_published_recently_with_future_poll (sts.PollMethodTests)
----------------------------------------------------------------------
Traceback (most recent call last):File "/dev/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_pollself.assertIs(future_poll.was_published_recently(), False)
AssertionError: True is not False----------------------------------------------------------------------
Ran 1 test in 0.003sFAILED (failures=1)
可以通过向./manage.py测试提供任意数量的“测试标签”来指定要运行的特定测试。每个测试标签都可以是包、模块、TestCase子类或测试方法的完整Python路径。例如:
# Run all the tests in sts module
$ ./manage.py sts# Run all the tests found within the 'animals' package
$ ./manage.py test animals# Run just one test case
$ ./manage.py sts.AnimalTestCase# Run just one test method
$ ./manage.py st_animals_can_speak
需要数据库支持的测试(即模型测试)时,不会使用真实的数据库,Django 会为测试创建单独的空白数据库。无论测试是通过还是失败,测试数据库都会在执行完所有测试后被销毁
。可以使用test --keepdb
选项阻止测试数据库被销毁。这将在运行之间保留测试数据库。如果数据库不存在,将首先创建它。还将应用任何迁移以使其保持最新。
在进行测试时,Django会无视DEBUG
的设置,所有测试都会在DEBUG=False
的环境下运行(除非设置--debug-mode
)。每次测试后都不会清除缓存,如果在生产中运行测试,运行manage.py test fooapp
可以将数据从测试插入到实时系统的缓存中。
django-admin test [test_label [test_label …]] ,运行所有应用下的单元测试
--failfast
: 在测试失败后立即停止运行测试并报告失败;
--testrunner TESTRUNNER
:控制用于执行测试的测试运行器类,使用TEST_RUNNER
设置提供的值。
--noinput, --no-input
:禁止所有用户提示。 典型的提示是关于删除现有测试数据库的警告。
--keepdb, -k
:在测试运行之间保留测试数据库。 这样做的优点是可以跳过create和destroy操作,这可以大大减少运行测试的时间,特别是在大型测试套件中。 如果测试数据库不存在,它将在第一次运行时创建,然后为每次后续运行保留。 在运行测试套件之前,任何未应用的迁移也将应用于测试数据库。
--reverse, -r
: 按相反的执行顺序
对测试用例进行排序。 这可能有助于调试未正确隔离的测试的副作用。 使用此选项时,将保留按测试类分组。
--debug-mode
:设置DEBUG=True
, 这可能有助于解决测试失败问题;
--debug-sql, -d
:为失败的测试启用SQL日志记录。 如果–verbosity为2,则还会输出传递测试中的查询;
--parallel [N]
: 在单独的并行进程中运行测试。 由于现代处理器具有多个内核,因此可以更快地运行测试。默认情况下–parallel根据multiprocessing.cpu_count()为每个核心运行一个进程。 您可以通过将其作为选项的值来调整进程数,例如, --parallel=4,或者通过设置DJANGO_TEST_PROCESSES
环境变量。
--tag TAGS
仅运行标有指定标签的测试。 可以多次指定并与test --exclude-tag结合使用。
exclude-tag EXCLUDE_TAGS
排除使用指定标记标记的测试。 可以多次指定并与test --tag结合使用。
Django提供了一小组在编写测试时派上用场的工具。
测试客户端是一个Python类,用它来充当虚拟Web浏览器,可以以编程方式测试视图并与Django的应用程序进行交互。
测试客户端的功能:
POST
和GET
请求,从HTTP的首部状态码到页面的所有内容;总结来说就是,使用Django的测试客户端来确定正在呈现正确的模板,并且模板传递正确的上下文数据。一个简单的例子,在test_client.py中测试GET
中文和英文的首页:
st import TestCase, Clientclass ClientTestCase(TestCase):def setUp(self):self.client = Client()def test_home_page_en(self):response = ('/en/')self.assertEqual(response.status_code, 200)self.assertTrue('Weicome to my site' t.decode('utf-8'))def test_home_page_zh(self):response = ('/zh-hans/')self.assertEqual(response.status_code, 200)self.assertTrue('欢迎来到我的网站' t.decode('utf-8'))
运行测试:
(venv) ulysses@ulysses:~/PycharmProjects/django_ulysses$ python manage.py test st_client
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.034sOK
Destroying test database for alias 'default'...
测试用户登录,和创建新topic,使用POST
请求:
def setUp(self):ate_user('Admin', 'admin@qq', 'admin12345')self.login = {'username': 'Admin', 'password': 'admin12345'}self.client = Client()def test_login(self):"""测试登录页面"""response = self.client.post('/en/users/login/', **self.login)self.assertEqual(response.status_code, 200)# self.assertTrue(self.client.login(**self.login))def test_post_topic(self):"""登录后,创建topic再读取"""self.client.login(**self.login)self.client.post('/en/new_topic/', {'text': '红尘多可笑,痴情最无聊'})topic = (text='红尘多可笑,痴情最无聊')self.assertIsNotNone(topic)response = (f'/en/topic/{topic.id}/')self.assertEqual(response.status_code, 200)self.t['topic'], topic)
有关test client可以参考官方文档:.1/topics/testing/tools/#the-test-client
LiveServerTestCase与TransactionTestCase基本相同,只有一个额外功能:它在安装时在后台启动一个实时Django服务器,并在拆卸时将其关闭。 这允许使用除Django虚拟客户端之外的自动测试客户端(例如Selenium客户端)在浏览器内执行一系列功能测试并模拟真实用户的操作。
实时服务器侦听localhost并绑定到端口0,端口0使用操作系统分配的空闲端口。 在测试期间,可以使用self.live_server_url
访问服务器的URL。首先安装selenium:
pip install selenium
在test_liveserver.py中添加一个使用Selenium客户端的测试:
st import LiveServerTestCase
from selenium.webdriver import Chrome, ChromeOptions
import reclass MySeleniumTests(LiveServerTestCase):""" 定位UI元素ID = "id"XPATH = "xpath"LINK_TEXT = "link text"PARTIAL_LINK_TEXT = "partial link text"NAME = "name"TAG_NAME = "tag name"CLASS_NAME = "class name"CSS_SELECTOR = "css selector""""# host = 'localhost'# port = 0@classmethoddef setUpClass(cls):super().setUpClass()options = ChromeOptions()options.add_argument('--headless')cls.selenium = Chrome(options)cls.selenium.implicitly_wait(10)@classmethoddef tearDownClass(cls):cls.selenium.quit()super().tearDownClass()def test_home_page(self):"""测试打开主页cls.live_server_url: '%s:%s' % (cls.host, cls.server_thread.port)"""(f"{self.live_server_url}/en/")self.assertTrue(re.search('Weicomes+tos+mys+site', self.selenium.page_source))
使用--headless
参数后,会打开一个无界面的浏览器。
测试登录,使用find_element_by_[]
来找寻网页上的可交互元素,使用click()
模拟显示的鼠标的点击:
def test_login(self):timeout = (f"{self.live_server_url}/en/")self.selenium.find_element_by_link_text('Login').click()WebDriverWait(self.selenium, timeout).until(lambda driver: driver.find_element_by_name('username'))self.selenium.find_element_by_name('username').clear()self.selenium.find_element_by_name('username').send_keys('Admin')self.selenium.find_element_by_name('password').clear()self.selenium.find_element_by_name('password').send_keys('admin12345')self.selenium.find_element_by_name('submit').click()# time.sleep(2)element = WebDriverWait(self.selenium, timeout).until(lambda driver: driver.find_element_by_id('navbarDropdown'))self.assertTrue(re.search('Weicomes+tos+mys+site', self.selenium.page_source))# self.selenium.find_element_by_id('navbarDropdown').click()element.click()# time.sleep(2)element = WebDriverWait(self.selenium, timeout).until(lambda driver: driver.find_element_by_name('profile'))element.click()# self.selenium.find_element_by_name('profile').click()self.assertTrue(re.search('<h2>Admin</h2>', self.selenium.page_source))
在切换页面时,使用WebDriverWait
检查是否加载了新的一页。
码覆盖率描述了已测试的源代码量。 它显示了代码的哪些部分是通过测试运行的,哪些不是。它是测试应用程序的重要部分。Django可以轻松地coverage.py
集成,后者是一种用于测量Python程序代码覆盖率的工具。 首先,安装 coverage: pip install coverage
。接下来,从包含manage.py的项目文件夹中运行以下命令:coverage run --source='.' manage.py test myapp
这将运行测试并收集项目中已执行文件的覆盖率数据。
可以通过键入以下命令来查看此数据的报告:coverage report
.
(venv) ulysses@ulysses:~/PycharmProjects/django_ulysses$ coverage run --source='.' manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).----------------------------------------------------------------------
Ran 0 tests in 0.000sOK
Destroying test database for alias 'default'...
(venv) ulysses@ulysses:~/PycharmProjects/django_ulysses$ coverage report
Name Stmts Miss Cover
---------------------------------------------------------------------------------------------------------------------------------
django_ulysses/__init__.py 2 0 100%
django_ulysses/database_router.py 0 0 100%
django_ulysses/settings.py 62 0 100%
django_ulysses/sitemaps.py 21 6 71%
使用coverage report -m
查看miss
的具体行号:
(venv) ulysses@ulysses:~/PycharmProjects/django_ulysses$ coverage report -m
Name Stmts Miss Cover Missing
-------------------------------------------------------------------------------------------------------------------------------------------
django_ulysses/__init__.py 2 0 100%
django_ulysses/database_router.py 0 0 100%
django_ulysses/settings.py 62 0 100%
django_ulysses/sitemaps.py 21 6 71% 13, 16, 21, 29, 32, 35
django_ulysses/urls.py 11 0 100%
django_ulysses/wsgi.py 8 8 0% 15-30
learning_logs/__init__.py 0 0 100%
使用coverage html
来生成完整的网页形式的报告:
如果数据库中没有任何数据,则基于数据库网站的测试用例没有多大用处。 在TestCase.setUpTestData()
使用ORM方法添加数据,使测试更具可读性,也可以选择使用fixture添加数据。
一个fixture就是导入Django数据库的数据集合。假如在项目指定的fixture文件目录settings.FIXTURE_DIRS
下已经有了数据,就可以指定一个fixtures
类属性来使用它:
st import TestCase
dels import Animalclass AnimalTestCase(TestCase):fixtures = ['mammals.json', 'birds']def setUp(self):# Test definitions as before.call_setup_methods()def test_fluffy_animals(self):# A test that uses the fixtures.call_some_test_code()
在进行测试时,Django设置一个测试数据库,该数据库对应于settings
中DATABASES定义中定义的每个数据库。 但是,运行Django TestCase所花费的大部分时间都被刷新调用所消耗,这确保了在每次测试运行开始时都有一个干净的数据库。 如果有多个数据库,则需要多次刷新(每个数据库一次)。这可能是一项耗时的活动 ,特别是如果测试不需要测试多数据库活动。
作为优化,Django仅在每次测试运行开始时刷新default
默认数据库。 如果您的设置包含多个数据库,并且您的测试要求每个数据库都是干净的,则可以使用测试套件上的multi_db
属性来请求完全刷新。
class TestMyViews(TestCase):multi_db = Truedef test_index_page_view(self):call_some_test_code()
在运行TestMyViews下的用例时,所有的测试数据库都会被刷新。multi_db标志还会影响TransactionTestCase.fixtures
加载到哪些数据库。 默认情况下(当multi_db = False
时),夹具仅加载到默认
数据库中。 如果multi_db = True
,则会将夹具加载到所有
数据库中。
使用SimpleTestCase.settings()
临时更改某一设置并在运行测试代码后可以恢复为原始值。 针对这点,Django提供了一个标准的Python上下文管理器,可以像这样使用:
st import TestCaseclass LoginTestCase(TestCase):def test_login(self):# First check for the default behaviorresponse = ('/sekrit/')self.assertRedirects(response, '/accounts/login/?next=/sekrit/')# Then override the LOGIN_URL settingwith self.settings(LOGIN_URL='/other/login/'):response = ('/sekrit/')self.assertRedirects(response, '/other/login/?next=/sekrit/')
例子中在with语句模块中重写了LOGIN_URL
,在这之外LOGIN_URL
就会回复原值。
使用列表
内的系统设置:
st import TestCaseclass MiddlewareTestCase(TestCase):def test_cache_middleware(self):dify_settings(MIDDLEWARE={'append': 'django.middleware.cache.FetchFromCacheMiddleware','prepend': 'django.middleware.cache.UpdateCacheMiddleware','remove': [ib.sessions.middleware.SessionMiddleware',ib.auth.middleware.AuthenticationMiddleware',ib.messages.middleware.MessageMiddleware',],}):response = ('/')# ...
若是对于一整个测试方法或类都要重写设置,可以使用override_settings()
或modify_settings()
装饰器:
st import TestCase, override_settings@override_settings(LOGIN_URL='/other/login/')
class LoginTestCase(TestCase):def test_login(self):response = ('/sekrit/')self.assertRedirects(response, '/other/login/?next=/sekrit/')
python内置的测试模块unittest.TestCase
提供了诸如assertTrue
、assertEqual
、assertIsNotNone
之类的断言,Django的TestCase
提供了除这些之外的断言。大多数这些断言方法给出的失败消息可以使用msg_prefix
参数进行自定义。 该字符串将以断言生成的任何失败消息为前缀。 这使您可以提供其他详细信息,以帮助您确定测试套件中故障的位置和原因,可以参考Assertions
使用@tag()
装饰器可以给测试添加标签来选择要进行那些测试。
st import tagclass SampleTestCase(TestCase):@tag('fast')def test_fast(self):...@tag('slow')def test_slow(self):...@tag('slow', 'core')def test_slow_but_core(self):...@tag('slow', 'core')class SampleTestCase(TestCase):...
在运行测试用例时,可以使用./manage.py test --tag=fast
来选择。
如果您的任何Django视图使用Django的电子邮件功能发送电子邮件,您可能不希望每次使用该视图运行测试时都发送电子邮件。出于这个原因,Django的测试运行器会自动将所有Django发送的电子邮件重定向到虚拟发件箱。这使您可以测试发送电子邮件的各个方面 ,从发送到每封邮件内容到邮件数量 ,而无需实际发送邮件。
进行测试时,会使用locmem
邮件后端时被调用。使用案例,检查ail.outbox的长度和内容:
import mail
st import TestCaseclass EmailTest(TestCase):def test_send_email(self):# Send message.mail.send_mail('Subject here', 'Here is the message.','from@example', ['to@example'],fail_silently=False,)# Test that one message has been sent.self.assertEqual(len(mail.outbox), 1)# Verify that the subject of the first message is correct.self.assertEqual(mail.outbox[0].subject, 'Subject here')
测试用的mail.outbox
在每次开始运行TestCase
时,都会被清空。可以使用mail.outbox = []
手动清空。
可以使用call_command()
函数测试管理命令。输出可以重定向到StringIO:
from io import StringIO
anagement import call_command
st import TestCaseclass ClosepollTest(TestCase):def test_command_output(self):out = StringIO()call_command('closepoll', stdout=out)self.assertIn('Expected output', value())
本文发布于:2024-02-05 08:36:13,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170728185364952.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |