Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pytest & Django are really good friend

Pytest & Django are really good friend

Slides of talk by Simone Dalla on testing of Django applications with pytest at Pycon7, DjangoVillage, Florence, Italy, April 2016.

Simone Dalla

April 16, 2016
Tweet

More Decks by Simone Dalla

Other Decks in Programming

Transcript

  1. PYCON SETTE about me Simone Dalla twitter @simodalla CTO @

    SIA Unione dei Comuni Valli del Reno Lavino e Samoggia (Bologna, IT) Pythonista and Djangonauta
  2. PYCON SETTE Topics: •testing and TDD overview •testing in Django

    overview •pytest and django-pytest intro •move from unittes to pytest https://[email protected]/simodalla/pycon7_pytest_django.git
  3. PYCON SETTE credits: @ThePraticalDev You’re a 10x hacker and it

    must be someone else’s fault!!! More books…
  4. PYCON SETTE from django.core.urlresolvers import reverse
 from django.utils import timezone


    from django.test import TestCase
 
 from ..models import Question class QuestionViewTests(TestCase):
 def test_index_view_with_no_questions(self):
 response = self.client.get(reverse('polls:index'))
 self.assertEqual(response.status_code, 200)
 self.assertContains(response, "No polls are available.")
 
 def test_index_view_with_a_past_question(self):
 create_question(question_text="Past question.", days=-30)
 response = self.client.get(reverse('polls:index'))
 self.assertQuerysetEqual(
 response.context['latest_question_list'],
 ['<Question: Past question.>']
 )
  5. PYCON SETTE 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
  6. PYCON SETTE many types of invocation: pytest - -help testing

    exception with context manager: raises marking test functions with attributes: markers monkeypatching/mocking modules and environments with fixture: monkeypatch many types of fixtures… Some cool features
  7. PYCON SETTE $ mkvirtualenv pycon7_pytest_django $ workon pycon7_pytest_django $ pip

    install django pytest-django psycopg2 $ createdb pycon7_pytest_django $ django-admin startproject pycon7_pytest_django *** $ cd pycon7_pytest_django *** $ python manage.py startapp polls *** Demo time! $ git clone https://[email protected]/simodalla/ pycon7_pytest_django.git $ git checkout 3f5316d ***
  8. PYCON SETTE class Question(models.Model):
 question_text = models.CharField(max_length=200)
 pub_date = models.DateTimeField('date

    published')
 
 def was_published_recently(self):
 now = timezone.now()
 return now - datetime.timedelta(days=1) <= self.pub_date <= now
 was_published_recently.admin_order_field = 'pub_date'
 was_published_recently.boolean = True
 was_published_recently.short_description = 'Published recently?'
 
 
 class Choice(models.Model):
 question = models.ForeignKey('Question', on_delete=models.CASCADE)
 choice_text = models.CharField(max_length=200)
 votes = models.IntegerField(default=0) models.py $ git checkout 44bd202
  9. PYCON SETTE class IndexView(generic.ListView):
 template_name = 'polls/index.html'
 context_object_name = 'latest_question_list'


    
 def get_queryset(self):
 """
 Return the last five published questions (not including those set to be published in the future).
 """
 return Question.objects.filter(
 pub_date__lte=timezone.now()).order_by('-pub_date')[:5]
 views.py $ git checkout 44bd202
  10. PYCON SETTE def create_question(question_text, days):
 """
 Creates a question with

    the given `question_text` and published the
 given number of `days` offset to now (negative for questions published
 in the past, positive for questions that have yet to be published).
 """
 time = timezone.now() + datetime.timedelta(days=days)
 return Question.objects.create(question_text=question_text, pub_date=time) class QuestionMethodTests(TestCase):
 
 def test_was_published_recently_with_future_question(self):
 """
 was_published_recently() should return False for questions whose
 pub_date is in the future.
 """
 time = timezone.now() + datetime.timedelta(days=30)
 future_question = Question(pub_date=time)
 self.assertEqual(future_question.was_published_recently(), False) 
 class QuestionViewTests(TestCase):
 
 def test_index_view_with_a_past_question(self):
 """
 Questions with a pub_date in the past should be displayed on the
 index page.
 """
 create_question(question_text="Past question.", days=-30)
 response = self.client.get(reverse('polls:index'))
 self.assertQuerysetEqual(
 response.context['latest_question_list'],
 ['<Question: Past question.>']
 ) tests.py $ git checkout 44bd202
  11. PYCON SETTE …demo time! $ python manage.py makemigrations $ python

    manage.py migrate $ python manage.py test Creating test database for alias 'default'... ........ ---------------------------------------------------- Ran 8 tests in 0.069s OK Destroying test database for alias 'default'...
  12. PYCON SETTE $ touch pytest.ini py.test!! [pytest] DJANGO_SETTINGS_MODULE=pycon7_pytest_django.settings $ py.test

    polls/tests.py ============== test session starts =================================== platform darwin -- Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 django settings: pycon7_pytest_django.settings (from ini file) rootdir: /Users/simo/PycharmProjects/pycon7_pytest_django, inifile: pytest.ini plugins: django-2.9.1 collected 8 items polls/tests.py ........ ============== 8 passed in 1.79 seconds =============================== $ git checkout 551305f
  13. PYCON SETTE By default, tests are found in test_*.py Discovery

    Just run doctest & unittes Run tests written for nose Discovery configuration in pytest.ini with: python_files = check_*.py
  14. PYCON SETTE $ py.test ===================== test session starts ========================= platform

    darwin -- Python 3.5.1, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 django settings: pycon7_pytest_django.settings (from ini file) rootdir: /Users/simo/PycharmProjects/pycon7_pytest_django, inifile: pytest.ini plugins: django-2.9.1 collected 8 items polls/tests/test_models.py ... polls/tests/test_views.py ..... ===================== 8 passed in 2.00 seconds ====================== Run all tests
  15. PYCON SETTE $ py.test tests/ Run all the tests in

    a specific directory $ py.test -k test_foo Run all the tests cases that are named *test_foo* $ py.test —-durations=4 Run tests and show 4 slowest setup/test durations (0 for all) $ py.test —-n=4 Run tests in four parallel processses (plugin pytest-xdist) $ py.test —-reuse-db Reuse database from last run $ py.test —-reuse-db —-create-db Reuse database schema if models if changed Run tests…
  16. PYCON SETTE # test_models.py
 
 import pytest def test_was_published_recently_with_future_question():
 pytest.fail()


    
 
 def test_was_published_recently_with_old_question():
 pytest.fail()
 
 
 def test_was_published_recently_with_recent_question():
 pytest.fail() unittest class $ git checkout ed46dd4 functions
  17. PYCON SETTE $ git checkout 5df1f99 def test_was_published_recently_with_future_question(self):
 """
 was_published_recently()

    should return False for questions whose
 pub_date is in the future.
 """
 time = timezone.now() + datetime.timedelta(days=30)
 future_question = Question(pub_date=time)
 self.assertEqual(future_question.was_published_recently(), False) def test_was_published_recently_with_recent_question():
 time = timezone.now() - datetime.timedelta(hours=1)
 recent_question = Question(pub_date=time)
 assert recent_question.was_published_recently() is True testing with assert
  18. PYCON SETTE $ git checkout c470411 @pytest.mark.parametrize("test_time,expected", [
 (timezone.now() +

    datetime.timedelta(days=30), False),
 (timezone.now() - datetime.timedelta(days=30), False),
 (timezone.now() - datetime.timedelta(hours=1), True),
 ])
 def test_was_published_recently(test_time, expected):
 question = Question(pub_date=test_time)
 assert question.was_published_recently() is expected tests parametrization $ py.test -v polls/tests/test_models.py::test_was_published_recently[test_time0-False] PASSED polls/tests/test_models.py::test_was_published_recently[test_time1-False] PASSED polls/tests/test_models.py::test_was_published_recently[test_time2-True] PASSED
  19. PYCON SETTE $ git checkout a8eb54a # test_views.py class QuestionViewTests(TestCase):


    def test_index_view_with_no_questions(self):
 """
 If no questions exist, an appropriate message should be displayed.
 """
 response = self.client.get(reverse('polls:index'))
 self.assertEqual(response.status_code, 200)
 self.assertContains(response, "No polls are available.") @pytest.mark.django_db
 def test_index_view_with_no_questions(client):
 response = client.get(reverse('polls:index'))
 assert response.status_code == 200
 assert "No polls are available." in str(response.content) pytest-django & database
  20. PYCON SETTE $ git checkout a8eb54a # no @pytest.mark.django_db def

    test_index_view_with_a_past_question(client):
 question = create_question(question_text="Past question.", days=-30)
 response = client.get(reverse('polls:index'))
 assert (list(response.context['latest_question_list']) == [question]) pytest-django & database $ py.test -v . . . test_views.py::test_index_view_with_a_past_question FAILED . . . Failed: Database access not allowed, use the "django_db" mark to enable it.
  21. PYCON SETTE $ git checkout a8eb54a pytest fixtures != django

    fixtures @pytest.mark.django_db def test_index_view_with_a_past_question(client):
 question = create_question(question_text="Past question.", days=-30)
 response = client.get(reverse('polls:index'))
 assert (list(response.context['latest_question_list']) == [question])
  22. PYCON SETTE django-pytest fixtures rf = instance of django.test.RequestFactory client

    = instance of django.test.Client admin_client = instance of django.test.Client that is logged in as an admin user admin_user = 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
  23. PYCON SETTE $ git checkout 4cecda0 pytest fixtures @pytest.fixture
 def

    question_in_the_past(db):
 return create_question(question_text="Past question.", days=-30)
 
 @pytest.fixture
 def question_in_the_future(db):
 return create_question(question_text="Future question.", days=30)
 
 @pytest.fixture
 def two_questions_in_the_past(db):
 return [create_question(question_text="Past question 1.", days=-30),
 create_question(question_text="Past question 2.", days=-5)] @pytest.mark.django_db def test_index_view_with_a_past_question(
 self, client, question_in_the_past):
 response = client.get(reverse('polls:index'))
 assert list(response.context['latest_question_list']) == [
 question_in_the_past] re
  24. PYCON SETTE my 2 cent on django fixtures… …use factory_boy!!!

    $ pip install factory_boy @pytest.fixture
 def two_questions_in_the_past(db):
 return [QuestionFactory.build(question_text=“Past question 1.", days=-30),
 QuestionFactory.build(question_text="Past question 2.", days=-5)] http://factoryboy.readthedocs.org/en/latest/index.html
  25. PYCON SETTE Crowdfunding a dev sprint in June 2016 for

    pytest 3.0 (+ tox) https://www.indiegogo.com/projects/python-testing-sprint-mid-2016 http://pytest.org/latest/announce/sprint2016.html
  26. PYCON SETTE More info? Help? Docs? Florian Bruhin, pytest -

    Rapid Simple Testing - Swiss Python Summit 2016 https://www.youtube.com/watch?v=rCBHkQ_LVIs 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.9/topics/testing/
 pytest documentation
 
 http://pytest.org/
 pytest-django documentation
 
 https://pytest-django.readthedocs.org