本文的翻译特别是为Python Web-Developer课程的学生准备的。
您使用命令运行测试manage.py test,但是您知道幕后发生了什么吗?测试运行器如何工作,如何将点E和F放置在屏幕上?
在学习Django的工作原理时,您会发现许多用例,例如更改cookie,设置全局标头和记录请求。同样,一旦了解了测试的工作原理,就可以自定义流程,例如以不同的顺序加载测试,在没有单独文件的情况下配置测试参数或阻止传出的HTTP请求。
在本文中,我们将对测试输出进行至关重要的定制,并将显示测试结果的样式从点和字母更改为表情符号。
但是,在编写代码之前,让我们重构测试过程。
测试输出
让我们看一下测试结果。让我们以一个空测试为基础的项目:
from django.test import TestCase
class ExampleTests(TestCase):
def test_one(self):
pass当我们运行测试时,我们得到熟悉的输出:
$ python manage.py test
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'... , , , -v 3:
$ python manage.py test -v 3
Creating test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
Operations to perform:
Synchronize unmigrated apps: core
Apply all migrations: (none)
Synchronizing apps without migrations:
Creating tables...
Running deferred SQL...
Running migrations:
No migrations to apply.
System check identified no issues (0 silenced).
test_one (example.core.tests.test_example.ExampleTests) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
Destroying test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')..., ! .
«Creating test database…» - Django . , .
SQLite, Django mode=memory . 10. , PostgreSQL, , in-memory.
«Operations to perform» – migrate . , manage.py migrate . , , .
«System check identified no issues». Django, « », . manage.py check, . , , , .
. , , . , .
. test runner , verbosity Django . «testone», , test runner «ok».
, «---». - , . «OK», , .
, .
:
.
.
.
.
/ .
.
, Django .
Django unittest
, , , Django unittest Python. , , , unittest, Django. :
.
«test»
, , — , Django manage.py test. django.core.management.commands.test.
, – 100 . handle() TestRunner. :
def handle(self, *test_labels, **options):
TestRunner = get_runner(settings, options['testrunner'])
...
test_runner = TestRunner(**options)
...
failures = test_runner.run_tests(test_labels)
... TestRunner? Django, . , , Django – django.test.runner.DiscoverRunner. .
DiscoverRunner
DiscoverRunner – . , , - .
- :
class DiscoverRunner:
"""A Django test runner that uses unittest2 test discovery."""
test_suite = unittest.TestSuite
parallel_test_suite = ParallelTestSuite
test_runner = unittest.TextTestRunner
test_loader = unittest.defaultTestLoader, . , – unittest.
, test_runner, , «test runner» — DiscoverRunner Django TextTestRunner unittest. DiscoverRunner , TextTestRunner, . , Django DiscoverRunner -, , TestCoordinator, .
DiscoverRunner runtests(). , run_tests() :
def run_tests(self, test_labels, extra_tests=None, **kwargs):
self.setup_test_environment()
suite = self.build_suite(test_labels, extra_tests)
databases = self.get_databases(suite)
old_config = self.setup_databases(aliases=databases)
self.run_checks(databases)
result = self.run_suite(suite)
self.teardown_databases(old_config)
self.teardown_test_environment()
return self.suite_result(suite, result). , :
setup_databases(). , ,get_databases(),SimpleTestCases, Django .migrate.run_checks().run_suite(), .teardown_databases().
, :
setup_test_environment()teardown_test_environment(), .suite_result().
, . Django. unittest - build_suite() run_suite().
.
buildsuite()
buildsuite() «suite». , , :
def build_suite(self, test_labels=None, extra_tests=None, **kwargs):
suite = self.test_suite()
test_labels = test_labels or ['.']
for label in test_labels:
tests = self.test_loader.loadTestsFromName(label)
suite.addTests(tests)
if self.parallel > 1:
suite = self.parallel_test_suite(suite, self.parallel, self.failfast)
return suite, , , DiscoverRunner:
test_suite- unittest, .parallel_test_suite- , Django.test_loader–unittest, .
runsuite()
DiscoverRunner, – run_suite(). , , :
def run_suite(self, suite, **kwargs):
kwargs = self.get_test_runner_kwargs()
runner = self.test_runner(**kwargs)
return runner.run(suite) – test runner . unittest, . unittest.TextTestRunner - test runner , , , XML- CI-.
, TextTestRunner.
TextTestRunner
unittest - . :
class TextTestRunner(object):
"""A test runner class that displays results in textual form.
It prints out the names of tests as they are run, errors as they
occur, and a summary of the results at the end of the test run.
"""
resultclass = TextTestResult
def __init__(self, ..., resultclass=None, ...): DiscoverRunner, . TextTestResult . DiscoverRunner, resultclass, TextTestRunner._init_().
- . .
, :
, , , DiscoverRunner. , , .
Django :
DiscoverRunner, TESTRUNNER .
– , DiscoverRunner. DiscoverRunner unittest , , .
Test Runner
, . DiscoverRunner runtests(), super():
# example/test.py
from django.test.runner import DiscoverRunner
class SuperFastTestRunner(DiscoverRunner):
def run_tests(self, *args, **kwargs):
print("All tests passed! A+")
failures = 0
return failures:
TEST_RUNNER = "example.test.SuperFastTestRunner" manage.py test, !
$ python manage.py test
All tests passed! A+, !
, !
, TextTestResult unittest . DiscoverRunner, resultclass TextTestRunner.
Django resultclass, , --debug-sql option, .
DiscoverRunner.run_suite() TextTestRunner DiscoverRunner.get_test_runner_kwargs():
<img alt="
def get_test_runner_kwargs(self):
return {
'failfast': self.failfast,
'resultclass': self.get_resultclass(),
'verbosity': self.verbosity,
'buffer': self.buffer,
} get_resultclass(), , (--debug-sql --pdb):
def get_resultclass(self):
if self.debug_sql:
return DebugSQLTextTestResult
elif self.pdb:
return PDBDebugResult , None, TextTestResult resultclass. None TextTestResult:
class EmojiTestRunner(DiscoverRunner):
def get_resultclass(self):
klass = super().get_resultclass()
if klass is None:
return EmojiTestResult
return klass EmojiTestResult TextTestResult . , :
class EmojiTestResult(unittest.TextTestResult):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# If the "dots" style was going to be used, show emoji instead
self.emojis = self.dots
self.dots = False
def addSuccess(self, test):
super().addSuccess(test)
if self.emojis:
self.stream.write('✅')
self.stream.flush()
def addError(self, test, err):
super().addError(test, err)
if self.emojis:
self.stream.write('?')
self.stream.flush()
def addFailure(self, test, err):
super().addFailure(test, err)
if self.emojis:
self.stream.write('❌')
self.stream.flush()
def addSkip(self, test, reason):
super().addSkip(test, reason)
if self.emojis:
self.stream.write("⏭")
self.stream.flush()
def addExpectedFailure(self, test, err):
super().addExpectedFailure(test, err)
if self.emojis:
self.stream.write("❎")
self.stream.flush()
def addUnexpectedSuccess(self, test):
super().addUnexpectedSuccess(test)
if self.emojis:
self.stream.write("✳️")
self.stream.flush()
def printErrors(self):
if self.emojis:
self.stream.writeln()
super().printErrors() TEST_RUNNER EmojiTestRunner, :
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
?❎❌⏭✅✅✅✳️
...
----------------------------------------------------------------------
Ran 8 tests in 0.003s
FAILED (failures=1, errors=1, skipped=1, expected failures=1, unexpected successes=1)
Destroying test database for alias 'default'...!
, unittest . , .
, , . , , . , , , . - unittest.
, DiscoverRunner:
unittest-xml-reporting XML CI-.
, , .
, pytest 700 . - , , Django. , , pytest - . pytest .
, pytest.
, . , - , Django , .