Чем отличается unittest в django от стандартной python библиотеки unittest
Перейти к содержимому

Чем отличается unittest в django от стандартной python библиотеки unittest

  • автор:

difference between django.test.TestCase vs unittest vs django.utils.unittest.TestCase

I am still using Django 1.2.1, and I think with the newer Django we don’t import unittest and then do unittest.TestCase .

Illustration

According to PyCon2011 talk, the second one is slightly more efficient.

Here is the diagram showing the relations:

enter image description here

So django.utils.unittest and django.test inherit from either unittest or unittest2 .

I am not sure if the following is correct or not. Please help editing.

In terms of efficiency, which one of the three is better? Many Django developers mock when they test, so sometimes database are not even necessary. Is there a way not creating tables when we run manage.py test myapp.MyClass ? For older version (prior to 1.3), which one is better?

1 Answer 1

Django’s TestCase enhances unittest.TestCase with some extra features:

  • Automatic loading of fixtures.
  • Wraps each test in a transaction.
  • Creates a TestClient instance.
  • Django-specific assertions for testing for things like redirection and form errors.

Generally speaking, you should most likely be using one of Django’s TestCase subclasses. Usually this will be django.test.TestCase , which, for efficiency, wraps the test in a DB transaction and uses rollback to ‘undo’ the test in the DB. If you need to manually manage transactions within your test, you would need to use django.test.TransactionTestCase , since you can’t start / rollback a transaction within a transaction.

There are some minor caveats to using django.test.TestCase , see the note here for more information.

If you’re just looking for a way to run your tests faster, have a look at running your tests in memory, and (if you’re using South), set SOUTH_TESTS_MIGRATE = False to tell South to use a (much faster) syncdb when creating the test DB, rather than running migrations.

Что происходит, когда вы выполняете manage.py test?

Вы запускаете тесты командой manage.py test , но знаете ли вы, что происходит под капотом при этом? Как работает исполнитель тестов (test runner) и как он расставляет точки, E и F на экране?

Когда вы узнаете, как работает Django, то откроете для себя множество вариантов использования, таких как изменение файлов cookie, установка глобальных заголовков и логирование запросов. Аналогично, поняв то, как работают тесты, вы сможете кастомизировать процессы, чтобы, например, загружать тесты в другом порядке, настраивать параметры тестирования без отдельного файла или блокировать исходящие HTTP-запросы.

В этой статье мы проведем жизненно важную настройку выходных данных ваших тестов, а еще сменим стиль отображения результатов выполнения тестов с точек и букв на эмодзи.

Однако, прежде чем писать код, давайте проведем реконструкцию процесса тестирования.

Выходные данные тестов

Давайте разберемся в результатах выполнения тестов. За основу возьмем проект с пустым тестом:

При выполнении тестов мы получаем знакомые выходные данные:

Чтобы понять, что происходит, попросим программу рассказать об этом подробнее, добавив флаг -v 3 :

Отлично, этого достаточно! Теперь давайте разбираться.

На первой строке мы видим сообщение «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. Если упрощать до трех основных строк:

Так что же представляет из себя класс TestRunner? Это компонент Django, который координирует процесс тестирования. Он настраиваемый, но класс по умолчанию, и единственный в самом Django – это django.test.runner.DiscoverRunner . Давайте рассмотрим его следующим.

Класс DiscoverRunner

DiscoverRunner – основной координатор процесса тестирования. Он обрабатывает добавление дополнительных аргументов команды управления, создание и передачу подкомпонентам, а также выполняет кое-какую настройку среды.

Начинается он как-то так:

Эти атрибуты класса указывают на другие классы, которые выполняют различные шаги в процессе тестирования. Как видите, большинство из них – компоненты unittest.

Обратите внимание на то, что один из них называется test_runner, таким образом у нас получается два различных понятия, который называются «test runner» — это DiscoverRunner из Django и TextTestRunner из unittest. DiscoverRunner делает гораздо больше, чем TextTestRunner , и у него другой интерфейс. Возможно, в Django можно было бы обозвать DiscoverRunner по-другому, например, TestCoordinator, но сейчас об этом уже поздно думать.

Основной поток в DiscoverRunner находится в методе runtests() . Если убрать кучу деталей, run_tests() будет выглядеть примерно так:

Шагов здесь совсем немного. Многие из методов соответствуют шагам из списка, который мы приводили выше:

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». Это длинный метод, но если его упростить, выглядеть он будет примерно так:

В этом методе используются три из четырех классов, к которым, как мы видели, обращается DiscoverRunner:

test_suite — компонент unittest, который служит контейнером для запуска тестов.

parallel_test_suite — оболочка для набора тестов, которая используется с функцией параллельного тестирования в Django.

test_loader – компонент unittest , который умеет находить тестовые модули на диске и загружать их в набор.

runsuite()

Еще один метод DiscoverRunner, о котором надо поговорить – это run_suite() . Его мы упрощать не будем, и просто посмотрим, как он выглядит:

Его единственная задача – создавать test runner и говорить ему запустить собранный набор тестов. Это последний из компонентов unittest, на который ссылается атрибут класса. Он использует unittest.TextTestRunner — test runner по умолчанию для вывода результатов в виде текста, в отличие, например, от XML-файла для передачи результатов в вашу CI-систему.

Закончим мы наше небольшое расследование, заглянув в класс TextTestRunner .

Класс TextTestRunner

Этот компонент unittest берет тест-кейс или набор и выполняет его. Начинается он вот так:

По аналогии с DiscoverRunner , он использует атрибут класса для ссылки на другой класс. Класс TextTestResult по умолчанию отвечает за текстовый вывод. В отличие от ссылок класса DiscoverRunner , мы можем переопределить resultclass , передав альтернативу TextTestRunner._init_() .

Теперь мы наконец-то можем кастомизировать процесс тестирования. Но сначала вернемся к нашему маленькому исследованию.

Карта

Теперь мы можем расширить карту и показать на ней классы, которые мы нашли:

Конечно, можно было добавить больше деталей, например, содержание нескольких важных методов из DiscoverRunner . Но того, что мы нашли, уже достаточно для реализации многих полезных настроек.

Как кастомизировать

Django предлагает два способа кастомизации процесса выполнения тестов:

Переопределить команду тестирования с помощью кастомного подкласса.

Переопределить класс DiscoverRunner, указав в настройках TESTRUNNER кастомный подкласс.

Поскольку команда запуска тестов – это просто, большую часть времени мы потратим на переписывание DiscoverRunner. Поскольку DiscoverRunner ссылается на компоненты unittest с помощью атрибутов класса, мы можем заменить их, переопределив атрибуты в нашем собственном подклассе.

Супербыстрый Test Runner

В качестве базового примера давайте представим, что нам нужно пропускать тесты и каждый раз уведомлять об успешном прохождении теста. Сделать это мы можем создав подкласс DiscoverRunner с новым методом runtests() , который не будет вызывать метод super() :

Затем воспользуемся им в файле с настройками следующим образом:

А затем выполним manage.py test , и получим результат за рекордно короткое время!

Отлично, очень полезно!

А теперь давайте сделаем еще практичнее, и перейдем к выводу результатов теста в виде эмодзи!

Вывод в виде эмодзи

Мы уже выяснили, что компонент TextTestResult из unittest отвечает за вывод. Мы можем заменить его в DiscoverRunner , передав значение resultclass в TextTestRunner .

В Django уже есть опции для замены resultclass , например, опция —debug-sql option, которая выводит выполненные запросы для неудачных тестов.

DiscoverRunner.run_suite() создает TextTestRunner с аргументами из метода DiscoverRunner.get_test_runner_kwargs() :

<img alt python»>def get_test_runner_kwargs(self): return

Он же в свою очередь вызывает get_resultclass() , который возвращает другой класс, если был использован один из двух параметров тестовой команды ( —debug-sql или —pdb ):

Если ни один из параметров не задан, метод неявно возвращает None, говоря TextTestResult использовать по умолчанию resultclass . Мы можем увидеть этот None в нашем собственном подклассе и заменить его подклассом TextTestResult :

Наш класс EmojiTestResult расширяет TextTestResult и заменяет вывод точек по умолчанию на эмодзи. В конечном итоге получается довольно длинно, поскольку на каждый тип результата приходится отдельный метод:

После указания TEST_RUNNER в EmojiTestRunner , мы можем запустить тесты и увидеть эмодзи:

Вместо заключения

После нашего спелеологического исследования мы увидели, что архитектура unittest относительно проста. Мы можем заменить классы на подклассы, чтобы изменить любое поведение в процессе тестирования.

Так работает кастомизация, специфичная для конкретного проекта, но кастомизации других людей использовать непросто. Так происходит потому, что архитектура заточена под наследование, а не под композицию. Мы должны использовать множественное наследование через сеть классов, чтобы объединить кастомизации, да и будет ли это все работать, зависит от качества их реализаций. На самом деле именно из-за этого не существует экосистемы плагинов для unittest.

Я знаком лишь с двумя библиотеками, предоставляющими кастомные подклассы DiscoverRunner :

unittest-xml-reporting обеспечивает вывод в формате XML для вашей CI-системы.

django-slow-tests обеспечивает измерение времени выполнения тесты для поиска самых медленных тестов.

Сам я не пробовал, но их объединение может не сработать, поскольку обе они переопределяют процесс вывода.

С другой стороны, у pytest есть процветающая экосистема с более чем 700 плагинами. Так происходит из-за того, что в его архитектуре используется композиция с хуками, которые работают по аналогии с сигналами в Django. Плагины регистрируются только для тех хуков, которые им нужны, а pytest вызывает каждую зарегистрированную хук-функцию в соответствующей точке процесса тестирования. Многие из встроенных функций pytest даже реализованы в виде плагинов.

Если вам интересна более детальная настройка процесса тестирования, обратитесь к pytest.

Конец

Спасибо, что отправились со мной в это путешествие. Я надеюсь, что вы узнали что-то новое о том, как Django запускает ваши тесты, и научились кастомизировать их.

Testing Django applications¶

This document is split into two primary sections. First, we explain how to write tests with Django. Then, we explain how to run them.

Writing tests¶

Django’s unit tests use a Python standard library module: unittest . This module defines tests in class-based approach.

Python 2.7 introduced some major changes to the unittest library, adding some extremely useful features. To ensure that every Django project can benefit from these new features, Django ships with a copy of unittest2, a copy of the Python 2.7 unittest library, backported for Python 2.5 compatibility.

To access this library, Django provides the django.utils.unittest module alias. If you are using Python 2.7, or you have installed unittest2 locally, Django will map the alias to the installed version of the unittest library. Otherwise, Django will use its own bundled version of unittest2.

To use this alias, simply use:

wherever you would have historically used:

If you want to continue to use the base unittest library, you can – you just won’t get any of the nice new unittest2 features.

For a given Django application, the test runner looks for unit tests in two places:

  • The models.py file. The test runner looks for any subclass of unittest.TestCase in this module.
  • A file called tests.py in the application directory – i.e., the directory that holds models.py . Again, the test runner looks for any subclass of unittest.TestCase in this module.

Here is an example unittest.TestCase subclass:

When you run your tests , the default behavior of the test utility is to find all the test cases (that is, subclasses of unittest.TestCase ) in models.py and tests.py , automatically build a test suite out of those test cases, and run that suite.

There is a second way to define the test suite for a module: if you define a function called suite() in either models.py or tests.py , the Django test runner will use that function to construct the test suite for that module. This follows the suggested organization for unit tests. See the Python documentation for more details on how to construct a complex test suite.

For more details about unittest , see the Python documentation.

If your tests rely on database access such as creating or querying models, be sure to create your test classes as subclasses of django.test.TestCase rather than unittest.TestCase .

In the example above, we instantiate some models but do not save them to the database. Using unittest.TestCase avoids the cost of running each test in a transaction and flushing the database, but for most applications the scope of tests you will be able to write this way will be fairly limited, so it’s easiest to use django.test.TestCase .

Running tests¶

Once you’ve written tests, run them using the test command of your project’s manage.py utility:

By default, this will run every test in every application in INSTALLED_APPS . If you only want to run tests for a particular application, add the application name to the command line. For example, if your INSTALLED_APPS contains ‘myproject.polls’ and ‘myproject.animals’ , you can run the myproject.animals unit tests alone with this command:

Note that we used animals , not myproject.animals .

You can be even more specific by naming an individual test case. To run a single test case in an application (for example, the AnimalTestCase described in the “Writing unit tests” section), add the name of the test case to the label on the command line:

And it gets even more granular than that! To run a single test method inside a test case, add the name of the test method to the label:

You can use the same rules if you’re using doctests. Django will use the test label as a path to the test method or class that you want to run. If your models.py or tests.py has a function with a doctest, or class with a class-level doctest, you can invoke that test by appending the name of the test method or class to the label:

If you want to run the doctest for a specific method in a class, add the name of the method to the label:

If you’re using a __test__ dictionary to specify doctests for a module, Django will use the label as a key in the __test__ dictionary for defined in models.py and tests.py .

If you press Ctrl-C while the tests are running, the test runner will wait for the currently running test to complete and then exit gracefully. During a graceful exit the test runner will output details of any test failures, report on how many tests were run and how many errors and failures were encountered, and destroy any test databases as usual. Thus pressing Ctrl-C can be very useful if you forget to pass the —failfast option, notice that some tests are unexpectedly failing, and want to get details on the failures without waiting for the full test run to complete.

If you do not want to wait for the currently running test to finish, you can press Ctrl-C a second time and the test run will halt immediately, but not gracefully. No details of the tests run before the interruption will be reported, and any test databases created by the run will not be destroyed.

Test with warnings enabled

It’s a good idea to run your tests with Python warnings enabled: python -Wall manage.py test . The -Wall flag tells Python to display deprecation warnings. Django, like many other Python libraries, uses these warnings to flag when features are going away. It also might flag areas in your code that aren’t strictly wrong but could benefit from a better implementation.

The test database¶

Tests that require a database (namely, model tests) will not use your “real” (production) database. Separate, blank databases are created for the tests.

Regardless of whether the tests pass or fail, the test databases are destroyed when all the tests have been executed.

By default the test databases get their names by prepending test_ to the value of the NAME settings for the databases defined in DATABASES . When using the SQLite database engine the tests will by default use an in-memory database (i.e., the database will be created in memory, bypassing the filesystem entirely!). If you want to use a different database name, specify TEST_NAME in the dictionary for any given database in DATABASES .

Aside from using a separate database, the test runner will otherwise use all of the same database settings you have in your settings file: ENGINE , USER , HOST , etc. The test database is created by the user specified by USER , so you’ll need to make sure that the given user account has sufficient privileges to create a new database on the system.

For fine-grained control over the character encoding of your test database, use the TEST_CHARSET option. If you’re using MySQL, you can also use the TEST_COLLATION option to control the particular collation used by the test database. See the settings documentation for details of these advanced settings.

Finding data from your production database when running tests?

If your code attempts to access the database when its modules are compiled, this will occur before the test database is set up, with potentially unexpected results. For example, if you have a database query in module-level code and a real database exists, production data could pollute your tests. It is a bad idea to have such import-time database queries in your code anyway — rewrite your code so that it doesn’t do this.

Order in which tests are executed¶

In order to guarantee that all TestCase code starts with a clean database, the Django test runner reorders tests in the following way:

  • First, all unittests (including unittest.TestCase , SimpleTestCase , TestCase and TransactionTestCase ) are run with no particular ordering guaranteed nor enforced among them.
  • Then any other tests (e.g. doctests) that may alter the database without restoring it to its original state are run.

The new ordering of tests may reveal unexpected dependencies on test case ordering. This is the case with doctests that relied on state left in the database by a given TransactionTestCase test, they must be updated to be able to run independently.

Other test conditions¶

Regardless of the value of the DEBUG setting in your configuration file, all Django tests run with DEBUG =False. This is to ensure that the observed output of your code matches what will be seen in a production setting.

Caches are not cleared after each test, and running “manage.py test fooapp” can insert data from the tests into the cache of a live system if you run your tests in production because, unlike databases, a separate “test cache” is not used. This behavior may change in the future.

Understanding the test output¶

When you run your tests, you’ll see a number of messages as the test runner prepares itself. You can control the level of detail of these messages with the verbosity option on the command line:

This tells you that the test runner is creating a test database, as described in the previous section.

Once the test database has been created, Django will run your tests. If everything goes well, you’ll see something like this:

If there are test failures, however, you’ll see full details about which tests failed:

A full explanation of this error output is beyond the scope of this document, but it’s pretty intuitive. You can consult the documentation of Python’s unittest library for details.

Note that the return code for the test-runner script is 1 for any number of failed and erroneous tests. If all the tests pass, the return code is 0. This feature is useful if you’re using the test-runner script in a shell script and need to test for success or failure at that level.

Speeding up the tests¶

In recent versions of Django, the default password hasher is rather slow by design. If during your tests you are authenticating many users, you may want to use a custom settings file and set the PASSWORD_HASHERS setting to a faster hashing algorithm:

Don’t forget to also include in PASSWORD_HASHERS any hashing algorithm used in fixtures, if any.

Testing tools¶

Django provides a small set of tools that come in handy when writing tests.

The test client¶

The test client is a Python class that acts as a dummy Web browser, allowing you to test your views and interact with your Django-powered application programmatically.

Some of the things you can do with the test client are:

  • Simulate GET and POST requests on a URL and observe the response – everything from low-level HTTP (result headers and status codes) to page content.
  • Test that the correct view is executed for a given URL.
  • Test that a given request is rendered by a given Django template, with a template context that contains certain values.

Note that the test client is not intended to be a replacement for Selenium or other “in-browser” frameworks. Django’s test client has a different focus. In short:

  • Use Django’s test client to establish that the correct view is being called and that the view is collecting the correct context data.
  • Use in-browser frameworks like Selenium to test rendered HTML and the behavior of Web pages, namely JavaScript functionality. Django also provides special support for those frameworks; see the section on LiveServerTestCase for more details.

A comprehensive test suite should use a combination of both test types.

Overview and a quick example¶

To use the test client, instantiate django.test.client.Client and retrieve Web pages:

As this example suggests, you can instantiate Client from within a session of the Python interactive interpreter.

Note a few important things about how the test client works:

The test client does not require the Web server to be running. In fact, it will run just fine with no Web server running at all! That’s because it avoids the overhead of HTTP and deals directly with the Django framework. This helps make the unit tests run quickly.

When retrieving pages, remember to specify the path of the URL, not the whole domain. For example, this is correct:

This is incorrect:

The test client is not capable of retrieving Web pages that are not powered by your Django project. If you need to retrieve other Web pages, use a Python standard library module such as urllib or urllib2 .

To resolve URLs, the test client uses whatever URLconf is pointed-to by your ROOT_URLCONF setting.

Although the above example would work in the Python interactive interpreter, some of the test client’s functionality, notably the template-related functionality, is only available while tests are running.

The reason for this is that Django’s test runner performs a bit of black magic in order to determine which template was loaded by a given view. This black magic (essentially a patching of Django’s template system in memory) only happens during test running.

By default, the test client will disable any CSRF checks performed by your site.

If, for some reason, you want the test client to perform CSRF checks, you can create an instance of the test client that enforces CSRF checks. To do this, pass in the enforce_csrf_checks argument when you construct your client:

Making requests¶

Use the django.test.client.Client class to make requests.

class Client ( enforce_csrf_checks=False, **defaults ) ¶

It requires no arguments at time of construction. However, you can use keywords arguments to specify some default headers. For example, this will send a User-Agent HTTP header in each request:

The values from the extra keywords arguments passed to get() , post() , etc. have precedence over the defaults passed to the class constructor.

The enforce_csrf_checks argument can be used to test CSRF protection (see above).

Once you have a Client instance, you can call any of the following methods:

Makes a GET request on the provided path and returns a Response object, which is documented below.

The key-value pairs in the data dictionary are used to create a GET data payload. For example:

. will result in the evaluation of a GET request equivalent to:

The extra keyword arguments parameter can be used to specify headers to be sent in the request. For example:

. will send the HTTP header HTTP_X_REQUESTED_WITH to the details view, which is a good way to test code paths that use the django.http.HttpRequest.is_ajax() method.

The headers sent via **extra should follow CGI specification. For example, emulating a different “Host” header as sent in the HTTP request from the browser to the server should be passed as HTTP_HOST .

If you already have the GET arguments in URL-encoded form, you can use that encoding instead of using the data argument. For example, the previous GET request could also be posed as:

If you provide a URL with both an encoded GET data and a data argument, the data argument will take precedence.

If you set follow to True the client will follow any redirects and a redirect_chain attribute will be set in the response object containing tuples of the intermediate urls and status codes.

If you had a URL /redirect_me/ that redirected to /next/ , that redirected to /final/ , this is what you’d see:

Makes a POST request on the provided path and returns a Response object, which is documented below.

The key-value pairs in the data dictionary are used to submit POST data. For example:

. will result in the evaluation of a POST request to this URL:

. with this POST data:

If you provide content_type (e.g. text/xml for an XML payload), the contents of data will be sent as-is in the POST request, using content_type in the HTTP Content-Type header.

If you don’t provide a value for content_type , the values in data will be transmitted with a content type of multipart/form-data. In this case, the key-value pairs in data will be encoded as a multipart message and used to create the POST data payload.

To submit multiple values for a given key – for example, to specify the selections for a <select multiple> – provide the values as a list or tuple for the required key. For example, this value of data would submit three selected values for the field named choices :

Submitting files is a special case. To POST a file, you need only provide the file field name as a key, and a file handle to the file you wish to upload as a value. For example:

(The name attachment here is not relevant; use whatever name your file-processing code expects.)

Note that if you wish to use the same file handle for multiple post() calls then you will need to manually reset the file pointer between posts. The easiest way to do this is to manually close the file after it has been provided to post() , as demonstrated above.

You should also ensure that the file is opened in a way that allows the data to be read. If your file contains binary data such as an image, this means you will need to open the file in rb (read binary) mode.

The extra argument acts the same as for Client.get() .

If the URL you request with a POST contains encoded parameters, these parameters will be made available in the request.GET data. For example, if you were to make the request:

. the view handling this request could interrogate request.POST to retrieve the username and password, and could interrogate request.GET to determine if the user was a visitor.

If you set follow to True the client will follow any redirects and a redirect_chain attribute will be set in the response object containing tuples of the intermediate urls and status codes.

Makes a HEAD request on the provided path and returns a Response object. This method works just like Client.get() , including the follow and extra arguments, except it does not return a message body.

options ( path, data=», content_type=’application/octet-stream’, follow=False, **extra ) ¶

Makes an OPTIONS request on the provided path and returns a Response object. Useful for testing RESTful interfaces.

When data is provided, it is used as the request body, and a Content-Type header is set to content_type .

The follow and extra arguments act the same as for Client.get() .

put ( path, data=», content_type=’application/octet-stream’, follow=False, **extra ) ¶

Makes a PUT request on the provided path and returns a Response object. Useful for testing RESTful interfaces.

When data is provided, it is used as the request body, and a Content-Type header is set to content_type .

The follow and extra arguments act the same as for Client.get() .

delete ( path, data=», content_type=’application/octet-stream’, follow=False, **extra ) ¶

Makes an DELETE request on the provided path and returns a Response object. Useful for testing RESTful interfaces.

When data is provided, it is used as the request body, and a Content-Type header is set to content_type .

The follow and extra arguments act the same as for Client.get() .

If your site uses Django’s authentication system and you deal with logging in users, you can use the test client’s login() method to simulate the effect of a user logging into the site.

After you call this method, the test client will have all the cookies and session data required to pass any login-based tests that may form part of a view.

The format of the credentials argument depends on which authentication backend you’re using (which is configured by your AUTHENTICATION_BACKENDS setting). If you’re using the standard authentication backend provided by Django ( ModelBackend ), credentials should be the user’s username and password, provided as keyword arguments:

If you’re using a different authentication backend, this method may require different credentials. It requires whichever credentials are required by your backend’s authenticate() method.

login() returns True if it the credentials were accepted and login was successful.

Finally, you’ll need to remember to create user accounts before you can use this method. As we explained above, the test runner is executed using a test database, which contains no users by default. As a result, user accounts that are valid on your production site will not work under test conditions. You’ll need to create users as part of the test suite – either manually (using the Django model API) or with a test fixture. Remember that if you want your test user to have a password, you can’t set the user’s password by setting the password attribute directly – you must use the set_password() function to store a correctly hashed password. Alternatively, you can use the create_user() helper method to create a new user with a correctly hashed password.

If your site uses Django’s authentication system , the logout() method can be used to simulate the effect of a user logging out of your site.

After you call this method, the test client will have all the cookies and session data cleared to defaults. Subsequent requests will appear to come from an AnonymousUser.

Testing responses¶

The get() and post() methods both return a Response object. This Response object is not the same as the HttpResponse object returned Django views; the test response object has some additional data useful for test code to verify.

Specifically, a Response object has the following attributes:

class Response ¶ client ¶

The test client that was used to make the request that resulted in the response.

The body of the response, as a string. This is the final page content as rendered by the view, or any error message.

The template Context instance that was used to render the template that produced the response content.

If the rendered page used multiple templates, then context will be a list of Context objects, in the order in which they were rendered.

Regardless of the number of templates used during rendering, you can retrieve context values using the [] operator. For example, the context variable name could be retrieved using:

The request data that stimulated the response.

The HTTP status of the response, as an integer. See RFC 2616#section-10 for a full list of HTTP status codes.

A list of Template instances used to render the final content, in the order they were rendered. For each template in the list, use template.name to get the template’s file name, if the template was loaded from a file. (The name is a string such as ‘admin/index.html’ .)

You can also use dictionary syntax on the response object to query the value of any settings in the HTTP headers. For example, you could determine the content type of a response using response[‘Content-Type’] .

Exceptions¶

If you point the test client at a view that raises an exception, that exception will be visible in the test case. You can then use a standard try . except block or assertRaises() to test for exceptions.

The only exceptions that are not visible to the test client are Http404 , PermissionDenied and SystemExit . Django catches these exceptions internally and converts them into the appropriate HTTP response codes. In these cases, you can check response.status_code in your test.

Persistent state¶

The test client is stateful. If a response returns a cookie, then that cookie will be stored in the test client and sent with all subsequent get() and post() requests.

Expiration policies for these cookies are not followed. If you want a cookie to expire, either delete it manually or create a new Client instance (which will effectively delete all cookies).

A test client has two attributes that store persistent state information. You can access these properties as part of a test condition.

A Python SimpleCookie object, containing the current values of all the client cookies. See the documentation of the Cookie module for more.

A dictionary-like object containing session information. See the session documentation for full details.

To modify the session and then save it, it must be stored in a variable first (because a new SessionStore is created every time this property is accessed):

Unit testing in Python

Unit tests and the TDD methodology (test-driven development, or development through testing) really justify themselves, as many projects have proven the practical benefits of their use. Unit testing takes software development to a new level of quality and maturity. Without unit testing, when the moment comes to choose between functionality, time, and quality, it is quality that always suffers. Typically, developers do not have enough time for testing and there is a race for the number of released functionalities. To eliminate the problems of testing and detecting errors, unit tests help to solve these problems with little additional effort. The Python programming language has a very good system for unit testing, and many frameworks support working with unit tests.

Why unit testing is important?

Quite often, there are many excuses about why unit tests are not used. This applies to both developers and customers. Probably the best summary of all the excuses is gathered in one place in the book "Pragmatic unit testing in Java using JUnit" by Andy Hunt and Dave Thomas. This book clearly shows that unit testing is one of the most important working methods of a senior developer.

Some developers may say, "my code is too difficult to test." However, even at the user interface level, this problem can be solved. There are a lot of approaches to how to test UI in Python frameworks.

If you have good code coverage with a set of tests, including a UI, you can always make new changes to the code, being fully confident that you haven't broken anything.

The second reason given for why code is too difficult to test says that the architectural design is too confusing. In this case, unit testing helps to create a good design. Using unit tests is very simple, so if you can select a separate layer with logic and write tests for it, then you can automatically verify the entire business logic. And in the absence of a domain model, you can use mock objects, as for this, there are many libraries in Python.

You can write thousands of lines of code very quickly and you will have success in the project right up to the point of satiety. The point of satiety in development comes when the addition of even the smallest and most harmless functionality generates all kinds of errors and it takes a lot of time to fix them. This is just where unit tests come in handy when you can localize the error in just a couple of minutes and improve your project’s architecture in a few hours. Properly designed unit tests have saved many thousands of projects in difficult situations.

What is unit testing?

Unit testing is a programming process that allows you to check the correctness of individual modules of the program source code.

The idea is to write tests for each non-trivial function or method. This allows you to quickly check whether the next change in the code has led to regression. meaning errors in the already tested places of the program. Additionally, such tests facilitate the elimination of such errors.

Using unit testing in Python

Python also has an effective testing system. For Python, the library unittest is a fairly effective and popular unit testing tool. This standard module for writing unit tests in Python has been developed for a long time. Unittest is actually a JUnit port with Java (with some modifications specifically for Python). This allows both the module code and the writing of tests to easily trace an object-oriented style, which is very convenient for testing methods and classes.

This tool has many features: checking received and expected values (assert), decorators that allow you to skip a separate test (@skip, @skipIf), or mark temporarily broken tests (@expectedFailure) and the list does not end there. Using asserts covers the needs of writing tests quite well. In addition to the basic functionality, unittest also provides the following options for improving tests:

  • you can collect tests into groups
  • collect test results (for example, for reporting)
  • OOP style reduces code duplication with similar test objects

In the use of unittest, there are several concepts. Let's look at them in order.

Test case: test case is the smallest unit of testing. It checks for a specific response for a specific set of input data.

Test suite: test suite is a collection of test cases. It is used to aggregate tests that must be run together.

Test fixture: test fixture is the fixed state of the objects used as the source when performing tests. The purpose of using fixture is if you have a complex test case, then preparing the desired state can easily take a lot of resources (for example, you consider a function with certain accuracy and each next sign of accuracy in the calculations takes a day). Using fixture (on slang — fixtures), we skip the preliminary preparation of the state and immediately begin testing. Test fixture can appear, for example, in the form (database state, set of environment variables, set of files with the necessary content).

Test runner: test runner is a component that organizes the execution of tests and provides the result to the user.

When writing tests in Python, one should proceed from the following principles that are common enough for all programming languages:

  • The test should not depend on the results of other tests.
  • The test should use data specially prepared for it, and no other.
  • The test should not require user input.
  • Tests should not overlap each other (do not write the same tests 20 times). You can write partially overlapping tests.
  • Finding a bug means writing a test.
  • Tests should be maintained in working condition.
  • Unit tests should not check the performance of an entity (class, function).
  • Tests should check not only that the entity is working correctly on the correct data, but also that it appropriately with incorrect data.
  • Tests should be run regularly

Different libraries and frameworks for unit testing on Python

Unittest

The most popular library for unit testing in Python is unittest. Let’s take a look at a simple example of unit testing in Python and unittest. Other unit test libraries work in the same manner as unittest. Consider the following code example:

This example shows a common template for most tests. There is an inheritance from TestCase, there are two simple tests, as well as an overload of the methods built into TestCase:

  • The def setUp (self) method is called BEFORE each test.
  • The def tearDown (self) method is called AFTER each test.
  • The def test_numbers_3_4() provides test for 3 and 4.
  • The def test_strings_a_3() provides a test for variables a and 3.

A list of similar ready-made functions such as for unittest in Python:

preparation of the test run; called before each test

called after the test has been run and the result is recorded. The method is launched even in case of an exception in the test body

the method is called before all class tests are run

called after running all class tests

called before running all module classes

called after running all module tests

PyTest

PyTest is an open-source Python-based testing framework. It is designed for all-purpose testing and has the capability for Functional and API testing:

  • More pythonic way of writing your tests.
  • Supports simple or complex code to test API, databases, and UIs.
  • Has a Simple python syntax.
  • Number of plugins.
  • Ability to run tests in parallel.
  • Ability to run selected specific subset of tests.

Robot

The popular Robot Framework is an open-source Automation Testing framework based on Python. This framework is developed in Python and also runs on Jython (JVM) and IronPython (.NET). Keyword style is used to write test cases in this framework. The Robot is supporting automation testing on cross-platform (Windows, Mac OS, and Linux) for desktop applications, mobile applications, web applications, etc.

The Robot framework is also used for Robotic Process Automation (RPA). The use of tabular data syntax, keyword-driven testing, rich libraries and toolsets, and parallel testing are advantages of this specific testing framework for Python.

Django

Django allows writing automated testing for modern web development. It is possible to use a collection of tests (a test suite) to solve, or avoid, a number of problems:

  • When you're writing new code, you can use tests to validate your code works as expected.
  • When you're refactoring or modifying old code, you can use tests to ensure your changes haven't affected your application's behavior unexpectedly.
  • Testing a Web application is a complex task because a Web application is made of several layers of logic, from HTTP-level request handling, to form validation and processing and to template rendering. With Django's test-execution framework and assorted utilities, it is possible to simulate requests, insert test data, inspect your application's output and generally verify your code is doing what it should be doing.

The preferred way to write tests in Django is using the unittest module built-in to the Python standard library. It is possible also to use any other Python test framework. Django provides an API and tools for that kind of integration.

Flask

Flask provides ample opportunity to test your web application using tests and by processing local context variables. To test the application, the add-on module is usually added as a separate file, and a skeleton is created in it to use the unittest module.

The code for the setUp() method creates a new test client and usually initializes a new database. This function is called before each individual test function is executed. To delete the database after the test is completed, you can use the tearDown() method. In addition, during the configuration process, the TESTING configuration flag is activated. This leads to disabling error trapping during the request processing, and error reports appear when executing test requests for the application.

Using self.app.get, a request is sent to the HTTP GET application with the specified path. The return value will be a response_class object. Now it is possible to use the appropriate attributes to check the return value as a string. The test client also provides the test_request_context() method, which can be used in conjunction and the “with” statement to temporarily activate the request context.

Conclusion

To conclude this article, it can be said that Python and frameworks that work with this programming language support unit tests very well. The recommendation of Svitla Systems' developers is that in projects on Python and its frameworks it is necessary to use unit testing. We have convinced many customers of the benefits of writing unit tests and they were very grateful to us when our teams could quickly localize the errors and fix them when creating new functionality. It also has a positive effect on the architecture of the project. Regardless of the Python framework that you use, it is necessary to write unit tests to cover the functionality of your system so that it can be successfully maintained in operation for many years.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *