DEV Community

Asdrubal Santander
Asdrubal Santander

Posted on

Setting up Pytest for a Django project

Hi, my name is Asdrubal Santander, I'm a Backend developer at Mo Tecnologías, and this is a short guide to how we implement testing with Pytest-Django.

Why Pytest?

Pytest it is an alternative framework to write tests in Python besides Unittest, between its best features it has:

  • Easy to write assert statements just like any other conditional assert 1 == 1. No more assertNotNull, assertDictEquals...
  • Fixtures to run code before and after each test, set up state of a test, load fixtures and more!
  • Better integration with plugins and tools (like pdb, and coverage).
  • Improved results, when a test fail it tells you exactly where it fails and with colors!
  • Can run tests written with the syntax of Unittest. So it makes easy to change.

Our Project structure:

project_name
├── project
│   ├── apps
│   │   ├── integration_apps
│   │   |   ├── app_name
|   |   |   |   └── tests
|   |   |   |       ├── component_to_be_tested
|   |   |   |       |   ├── test_function_name.py
|   |   |   |       |
|   |   |   |       ├── conftest.py
|   |   |   |       └── ...
|   |   |   └── ...
|   |   └── ...
│   ├── project_settings
│   |   └── settings
|   |       ├── development_settings
|   |       ├── production_settings
|   |       └── test_settings
|   └── manage.py
├── pytest.ini
└── ...
Enter fullscreen mode Exit fullscreen mode

So as you can see, our Django project as an uncommon structure, I will try to explain in a general way:

  • project_name: config files, requirements folder, README.MD ...
  • project: apps, fixtures folder, project_settings and manage.py.
  • project_settings: settings, project urls.py, wsgi.py.
  • settings: settings for our project in this case we are going to be focus on test_settings.
  • apps: contains our business logic in separated apps.
  • test: inside this folder we have separated conftest.py to an specific app, and is where we are going to put all our tests, separated by folders such like "views", "services", "selectors"...

Installation:

pip install pytest-django
pip install pytest-cov
pip install pytest-pythonpath
Enter fullscreen mode Exit fullscreen mode
  • pytest-django: A plugin to easily integrate Pytest with Django.
  • pytest-cov: Plugin that helps to create the coverage reports inside Pytest.
  • pytest-pythonpath: Plugin that modifies the env var PYTHONPATH. This is useful when pytest can't find your manage.py

pytest.ini:

[pytest]
addopts = -v -p no:warnings --nomigrations --cov=. --no-cov-on-fail
DJANGO_SETTINGS_MODULE = project.project_settings.settings.test_settings
python_paths = .
Enter fullscreen mode Exit fullscreen mode

With the file pytest.ini we can set arguments and other configurations, so we don't have to specify each time we run Pytest.

  • addopts: Special variable to set command line options

    • -v: To tell pytest to be a little bit more verbose
    • -p no:warnings: Disable warnings.
    • --nomigrations: Disable the migrations when run pytest this could make the setup for testing faster.
    • --cov=.: When pytest runs it makes a coverage report.
    • --no-cov-on-fail: Don't show the coverage report if a test fails.
  • DJANGO_SETTINGS_MODULE: This is where we tell pytest to run with a specific settings.

  • python_paths: The dot meaning that the current directory will be added to the PYTHONPATH env var.

Running Pytest:

$ pytest 

======================================== test session starts ========================================
platform darwin -- Python 3.6.0, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- 
...
cachedir: .pytest_cache
Django settings: project.project_settings.settings.test_settings (from ini file)
rootdir: .../mysite, inifile: pytest.ini
plugins: django-3.7.0, pythonpath-0.7.3, cov-2.8.1
collected 0 items



---------- coverage: platform darwin, python 3.6.0-final-0 -----------
Name                                                                Stmts   Miss  Cover
---------------------------------------------------------------------------------------
...
mo_services/apps/importantapp/test/services/test_function_name.py       0      0   100%
...
--------------------------------------------------------------------------------------------
TOTAL                                                                   X     X    XXX%


======================================= no tests ran in 0.09s =======================================
Enter fullscreen mode Exit fullscreen mode

Let's write a example test:

Inside of app_name we create a python package named tests, this package will have all the tests for this application divided in sub packages with the name of the component:

# .../apps/integration_apps/app_name/tests/component_to_be_tested/test_function_name.py

from app_name.component_name import some_function


class TestFunctionName:

    def test_example():
        result = some_function()

        expected_result = 'some_result'

        assert result == expected_result

Enter fullscreen mode Exit fullscreen mode

Running pytest again:

$ pytest 
======================================== test session starts ========================================
platform darwin -- Python 3.6.0, pytest-5.2.2, py-1.8.0, pluggy-0.13.0 -- 
...
cachedir: .pytest_cache
Django settings: project.project_settings.settings.test_settings (from ini file)
rootdir: .../mysite, inifile: pytest.ini
plugins: django-3.7.0, pythonpath-0.7.3, cov-2.8.1
collected 1 item

apps/integration_apps/app_name/tests/component_to_be_tested/test_function_name.py::TestFunctionName::test_example PASSED [100%]

--------------- coverage: platform darwin, python 3.6.0-final-0 -----------
Name                                                                Stmts   Miss  Cover
--------------------------------------------------------------------------------------------
...
mo_services/apps/importantapp/test/services/test_function_name.py       1      0   100%
...
--------------------------------------------------------------------------------------------
TOTAL                                                                   X     X    XXX%

========================================= 1 passed in 0.11s =========================================
Enter fullscreen mode Exit fullscreen mode

So now you have successfully run Pytest in Django!

Some final cool command line options:

  • -x: This option tells pytest to stop after the first test fail, is great when we are having problems with several tests so in this way we can solve this problems one by one.
  • --nf: Tells pytest to run the "new tests" first.
  • --ff: Tells pytest to run the "fail tests" first.
  • --lf: Tells pytest to run the only the "last fail".

Where to find more:

Top comments (1)

Collapse
 
sobolevn profile image
Nikita Sobolev

Or you can use github.com/wemake-services/wemake-...
It comes with pytest already configured. With lots of extra features!

GitHub logo wemake-services / wemake-django-template

Bleeding edge django template focused on code quality and security.

wemake-django-template

wemake.services Awesome Build status Documentation Status Dependencies Status wemake-python-styleguide

Bleeding edge django2.2 template focused on code quality and security.


Purpose

This project is used to scaffold a django project structure Just like django-admin.py startproject but better.

Features

Installation

Firstly, you will need to install dependencies:

pip install cookiecutter jinja2-git
Enter fullscreen mode Exit fullscreen mode

Then, create a project itself:

cookiecutter gh:wemake-services/wemake-django-template
Enter fullscreen mode Exit fullscreen mode

Who are using this template?

If you use our template, please add yourself or your company in the list.

We offer free email support for anyone who is using this If you have any problems or questions,…