Slide 1

Slide 1 text

PyCon 6, DjangoVillage - Florence, 17 April 2015 Testing of Django applicatons with pytest

Slide 2

Slide 2 text

CTO @ SIA Unione dei Comuni Valli del Reno Lavino e Samoggia (Bologna, IT), Pythonista and Django programmer, I use Python into my work environment for.....ALL! about me Simone Dalla twitter @simodalla slides http://speakerdeck.com/simodalla

Slide 3

Slide 3 text

Testing in Python unittest usually...

Slide 4

Slide 4 text

Testing in Django unittest & django testing tools usually...

Slide 5

Slide 5 text

pytest a mature full-featured Python testing tool

Slide 6

Slide 6 text

import unittest class TestFoo(unittest.TestCase): def test_bar(self): self.assertEqual(True, True) def test_bar(self): assert True == True no-boilerplate testing...

Slide 7

Slide 7 text

plugin driven - pytest-django, Django integration - pytest-cov, coverage reporting - pytest-xdist, distributed/parallelized tests - … and more at: http://pytest.org/latest/plugins.html#external-plugins

Slide 8

Slide 8 text

discovery - just run doctest and unittest - run any function prefixed with test_ as a test - run tests written for nose address/ __init__.py envelope.py geo.py test_envelope.py # checked for tests test_geo.py # checked for tests records/ # pytest WON'T look here because it lacks __init__.py records.csv records.py test_records.py # skipped because records/ lacks __init__.py __init__.py main.py test_main.py # checked for tests

Slide 9

Slide 9 text

testing exception with context manager raises import pytest def test_an_exception(): with pytest.raises(IndexError) as excinfo: [1,2,3][100] assert 'out of range' in str(excinfo.value)

Slide 10

Slide 10 text

marking test functions with attributes, markers import pytest @pytest.mark.xfail def test_yes_this_fail(): ... @pytest.mark.skipif(sys.version_info<(3.3), reason='requires py3.3') def test_new_py33_feature(): ...

Slide 11

Slide 11 text

change behaviour # pytest.ini (or tox.ini or setup.cfg) [pytest] # You must put pytest-related # controls in a 'pytest' block python_files=*.py # Run tests against all # python modules norecursedirs = _build # Don't look inside of # _build directories # conftest.py collect_ignore = ["setup.py", "conftest.py"]

Slide 12

Slide 12 text

pytest fixtures - Replacement for unittest setUp/tearDown - Django fixture != pytest fixture [{"pk": 1, "model": "tracker.loggedtimebreak", "fields": {"start": "2011-­‐08-­‐09T09:10:00Z", "is_scheduled": true, "stop": "2011-­‐08-­‐09T09:40:00Z", "logged_time": 31946}}, {"pk": 2, "model": "tracker.loggedtimebreak", "fields": {"start": "2011-­‐08-­‐10T09:00:00Z", "is_scheduled": true, "stop": "2011-­‐08-­‐10T09:30:00Z", "logged_time": 32038}}, {"pk": 3, "model": "tracker.loggedtimebreak", "fields": {"start": "2011-­‐08-­‐10T09:10:00Z", "is_scheduled": true, "stop": "2011-­‐08-­‐10T09:40:00Z", "logged_time": 32039}}, {"pk": 4, "model": "tracker.loggedtimebreak", "fields": {"start": "2011-­‐08-­‐11T09:10:00Z", "is_scheduled": true, "stop": "2011-­‐08-­‐11T09:40:00Z", "logged_time": 32104}}, {"pk": 5, "model": "tracker.loggedtimebreak", "fields": {"start": "2011-­‐08-­‐11T09:00:00Z", "is_scheduled": true, "stop": "2011-­‐08-­‐11T09:30:00Z", "logged_time": 32105}}, {"pk": 6, "model": "tracker.loggedtimebreak", "fields": {"start": "2011-­‐08-­‐12T09:00:00Z", ……………...

Slide 13

Slide 13 text

pytest-django Integration between pytest and Django

Slide 14

Slide 14 text

Install pytest-django and pytest pip install pytest-django installation

Slide 15

Slide 15 text

configure Django settings in pytest.ini [pytest] DJANGO_SETTINGS_MODULE=settings

Slide 16

Slide 16 text

Django TestCase just works!!

Slide 17

Slide 17 text

running the tests!!!

Slide 18

Slide 18 text

Run all the tests $ py.test Run all the tests in a specific file $ py.test test_views.py Run all the tests in a specific directory $ py.test tests/ Run all the tests cases that are named *test_foo* $ py.test -k test_foo

Slide 19

Slide 19 text

run tests in parallel Run tests in four parallel processes $ py.test -n 4 pip install pytest-xdist Install xdist plugin that provide distributed testing

Slide 20

Slide 20 text

...no-boilerplate testing def test_hello_world(client): response = client.get(‘/home/’) assert response.status_code == 200 assert ‘Hello’ in response.content.decode(‘utf-8’) from django.test import TestCase class TestHelloWorld(TestCase): def test_hello_world(self): response = self.client.get(‘/home/’) self.assertEqual(response.status_code, 200) self.assertIn(‘Hello’, response.content.decode(‘utf-8’))

Slide 21

Slide 21 text

“H. Percival - Obidire alla capra” https://www.youtube.com/watch?v=u0L3DuAPOVw

Slide 22

Slide 22 text

test database reuse Reuse database from last run $ py.test --reuse-db Put this in pytest.ini to always reuse the database [pytest] addopts = --reuse-db Recreate database schema if models is changed $ py.test --reuse-db --create-db

Slide 23

Slide 23 text

pytest-django and database import pytest from polls.models import Question @pytest.mark.django_db def test_how_many_questions(): assert Question.object.count() == 0 This will allow database access

Slide 24

Slide 24 text

import pytest from polls.models import Question def test_how_many_questions(): assert Question.object.count() == 0 _________test_how_many_questions__________ def test_how_many_questions(): > Question.objects.count() == 0 … E Failed: Database access not allowed, use the "django_db" mark to enable FAIL!

Slide 25

Slide 25 text

import pytest pytestmark = pytest.mark.django_db def test_one(): pass def test_two(): pass Mark all tests into module for database access

Slide 26

Slide 26 text

$ pytest -m ‘not django_db’ Run all tests which does not access to db

Slide 27

Slide 27 text

pytest fixture # conftest.py import pytest from django.utils import timezone from polls.models import Question @pytest.fixture def question_in_db(db): return Question.objects.create( question_text="What's new?", pub_date=timezone.now()) # test_models.py from polls.models import Question def test_db_has_one_question(question_in_db): assert Question.objects.count() == 1 assert question_in_db.question_text == "What's new?" pytest fixture != django fixture

Slide 28

Slide 28 text

django-pytest helpers ● rf = instance of django.test.RequestFactory ● client = instance of django.test.Client ● admin_client = instance of a superuser, with username “admin” and password “password” ● django_user_model = user model used by Django ● django_username_field = field name used for the username on the user model ● db = ensure the Django database is set up ● transactional_db = database including transaction support ● live_server = runs a live Django server in a background thread ● settings = provide a handle on the django settings module ● pytest.mark.django_db = mark a test function as requiring the database ● pytest.mark.urls = override the urlconf, specifing a different settings. ROOT_URLCONF module for the marked tests FIXTURES MARKERS

Slide 29

Slide 29 text

More pytest fixtures feauters ● writing test for fixtures ● parametrization ● scoping (reuse expensive setup) ● auto use fixtures (=~ setUp of unittest) ● executing “finalization/teardown” code ● modularity, using fixture from a fixture ● introspect requesting test context

Slide 30

Slide 30 text

More info? Help? Docs? ● Carl Meyer, Testing and Django - Pycon 2012: https://www.youtube.com/watch?v=ickNQcNXiS4 ● Holger Krekel, Improving automated testing with py.test - PyCon 2014 https://www.youtube.com/watch?v=AiThU6JQbE8 ● Andreas Pelme, Pytest: help you write better Django apps, DjangoCon Europe 2014 https://www.youtube.com/watch?v=aaArYVh6XSM ● official Django testing documentation https://docs.djangoproject.com/en/1.8/topics/testing/ ● pytest documentation http://pytest.org/ ● pytest-django documentation https://pytest-django.readthedocs.org

Slide 31

Slide 31 text

Thank you!!! .... questions? email [email protected] twitter @simodalla github simodalla linkedIn http://it.linkedin.com/in/simodalla slides http://speakerdeck.com/simodalla