Que se passe-t-il lorsque vous exécutez le test manage.py?

La traduction de l'article a été préparée spécialement pour les étudiants du cours Python Web-Developer .


Vous exécutez les tests avec une commande manage.py test, mais savez-vous ce qui se passe sous le capot? Comment fonctionne le testeur et comment place-t-il les points, E et F sur l'écran?

En apprenant comment Django fonctionne, vous découvrirez de nombreux cas d'utilisation, tels que la modification des cookies, la définition d'en-têtes globaux et la journalisation des demandes. De même, une fois que vous avez compris le fonctionnement des tests, vous pouvez personnaliser les processus pour, par exemple, charger les tests dans un ordre différent, configurer les paramètres de test sans fichier séparé ou bloquer les requêtes HTTP sortantes.

Dans cet article, nous passerons en revue la personnalisation vitale de la sortie de vos tests et changerons également le style d'affichage des résultats des tests, des points et des lettres en emoji.

, , .

. :

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», , .

, .

:

  1. .

  2. .

  3. .

  4. .

  5. / .

  6. .

, 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_loaderunittest, .

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. 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:

, , .

, pytest 700 . - , , Django. , , pytest - . pytest .

, pytest.

, . , - , Django , .

:




All Articles