Hypothesis for Django

https://bit.ly/hypothesis-for-django

Who am I?

  • David R. MacIver. The R is for namespacing purposes.
  • I wrote Hypothesis.
  • But right here and now, I have no idea what I'm doing.

What is Hypothesis?

  • Testing library based on the Haskell library, Quickcheck.
  • Designed to be very Pythonic (you don't need to know what a monad is).
  • You write the tests, Hypothesis gives you the examples.
  • Integrates well with your existing testing.
  • Has (some) special support for Django.

The Setup

  • We are testing the backend of a Django application.
  • We have two models: User and Project.
  • Each project has a set of Users who collaborate on it.
  • A project has a maximum number of allowed collaborators.
  • If you try to go above that, you get an error.

from .models import User, Project
from django.test import TestCase

class TestProjectManagement(TestCase):
    def test_can_add_users_up_to_collaborator_limit(self):
        project = Project.objects.create(
            collaborator_limit=3,
            name="Some project",
        )
        alex = User.objects.create(email="alex@example.com")
        kim = User.objects.create(email="kim@example.com")
        pat = User.objects.create(email="pat@example.com")
        project.add_user(alex)
        project.add_user(kim)
        project.add_user(pat)
        self.assertTrue(project.team_contains(alex))
        self.assertTrue(project.team_contains(kim))
        self.assertTrue(project.team_contains(pat))
        

from .models import User, Project
from hypothesis.extra.django import TestCase
from hypothesis import given
from hypothesis.extra.django.models import models
from hypothesis.strategies import just

class TestProjectManagement(TestCase):
    @given(
        models(Project, collaborator_limit=just(3)),
        models(User), models(User), models(User))
    def test_can_add_users_up_to_collaborator_limit(
        self, project, alex, kim, pat
    ):
        project.add_user(alex)
        project.add_user(kim)
        project.add_user(pat)
        self.assertTrue(project.team_contains(alex))
        self.assertTrue(project.team_contains(kim))
        self.assertTrue(project.team_contains(pat))
        

from .models import User, Project
from hypothesis.extra.django import TestCase
from hypothesis import given
from hypothesis.extra.django.models import models
from hypothesis.strategies import just, lists

class TestProjectManagement(TestCase):
    @given(
        models(Project, collaborator_limit=just(3)),
        lists(models(User), min_size=3, max_size=3))
    def test_can_add_users_up_to_collaborator_limit(
        self, project, collaborators
    ):
        for c in collaborators:
            project.add_user(c)
        for c in collaborators:
            self.assertTrue(project.team_contains(c))
        


from .models import User, Project
from hypothesis.extra.django import TestCase
from hypothesis import given, assume
from hypothesis.extra.django.models import models
from hypothesis.strategies import lists, integers

class TestProjectManagement(TestCase):
    @given(
        models(Project, collaborator_limit=integers(
            min_value=0, max_value=20)),
        lists(models(User), max_size=20))
    def test_can_add_users_up_to_collaborator_limit(
        self, project, collaborators
    ):
        assume(len(collaborators) <= project.collaborator_limit)
        for c in collaborators:
            project.add_user(c)
        for c in collaborators:
            self.assertTrue(project.team_contains(c))
    


from .models import User, Project
from hypothesis.extra.django import TestCase
from hypothesis import given
from hypothesis.extra.django.models import models
from hypothesis.strategies import lists, integers

class TestProjectManagement(TestCase):
    @given(
        models(Project, collaborator_limit=integers(
            min_value=0, max_value=20)),
        lists(models(User), max_size=20))
    def test_can_add_users_up_to_collaborator_limit(
        self, project, collaborators
    ):
        for c in collaborators:
            project.add_user(c)
        for c in collaborators:
            self.assertTrue(project.team_contains(c))
    


Falsifying example: test_can_add_users_up_to_collaborator_limit(
  self=TestProjectManagement(),
  project=Project('', 0),
  collaborators=[User(.@.com)]
)
Traceback (most recent call last):
(...)
  raise LimitReached()
manager.models.LimitReached
        


from .models import User, Project, LimitReached
from hypothesis.extra.django import TestCase
from hypothesis import given
from hypothesis.extra.django.models import models
from hypothesis.strategies import lists, integers

class TestProjectManagement(TestCase):
    @given(
        models(Project, collaborator_limit=integers(
            min_value=0, max_value=20)),
        lists(models(User), max_size=20))
    def test_can_add_users_up_to_collaborator_limit(
        self, project, collaborators
    ):
        for c in collaborators:
            if project.at_collaboration_limit():
                with self.assertRaises(LimitReached):
                    project.add_user(c)
                self.assertFalse(project.team_contains(c))
            else:
                project.add_user(c)
                self.assertTrue(project.team_contains(c))
        

Falsifying example: test_can_add_users_up_to_collaborator_limit(
  self=TestProjectManagement(),
  project=Project('', 1),
  collaborators=[User(.@.biz), User(.@.biz)]
)
Traceback (most recent call last):
...
    self.assertFalse(project.team_contains(c))
AssertionError: True is not false
        


from .models import User, Project, LimitReached
from hypothesis.extra.django import TestCase
from hypothesis import given
from hypothesis.extra.django.models import models
from hypothesis.strategies import lists, integers

class TestProjectManagement(TestCase):
    @given(
        models(Project, collaborator_limit=integers(
            min_value=0, max_value=20)),
        lists(models(User), max_size=20))
    def test_can_add_users_up_to_collaborator_limit(
        self, project, collaborators
    ):
        for c in collaborators:
            if (
                project.at_collaboration_limit() and
                not project.team_contains(c)
            ):
                with self.assertRaises(LimitReached):
                    project.add_user(c)
                self.assertFalse(project.team_contains(c))
            else:
                project.add_user(c)
                self.assertTrue(project.team_contains(c))
        

Falsifying example: test_can_add_users_up_to_collaborator_limit(
  self=TestProjectManagement(),
  project=Project('', 1),
  collaborators=[
    User(.@.com),
    User(.@.com)
  ]
)
Traceback (most recent call last):
...
    raise LimitReached()
manager.models.LimitReached

        

def add_user(self, user):
    if self.at_collaboration_limit():
        raise LimitReached()
    self.collaborators.add(user)
    

def add_user(self, user):
    if self.team_contains(user):
        return
    if self.at_collaboration_limit():
        raise LimitReached()
    self.collaborators.add(user)
    

.
----------------------------------------------
Ran 1 test in 1.833s
        

Obligatory plug

I offer Hypothesis training and support.

(I'm also potentially available for other contracting work)

drmaciver.com / @DRMacIver

https://hypothesis.readthedocs.org/

https://bit.ly/hypothesis-for-django