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

[DjangoCon US] Testing in Django

[DjangoCon US] Testing in Django

Keynote about testing in/with/within Django presented at DjangoCon US 2017.

Ana Balica

August 15, 2017
Tweet

More Decks by Ana Balica

Other Decks in Programming

Transcript

  1. #2333 As an added incentive, this is a feature that

    is present in Rails. Add unit test framework for end-user Django applications ”
  2. 1.1

  3. 1.2

  4. 1.2 multiple databases DATABASES = { 'default': { 'HOST': 'dbprimary',

    # ... plus other settings }, 'replica': { 'HOST': 'dbreplica', 'TEST_MIRROR': 'default', # ... plus other settings } }
  5. 1.2 multiple databases DATABASES = { 'default': { 'HOST': 'dbprimary',

    # ... plus other settings }, 'replica': { 'HOST': 'dbreplica', 'TEST_MIRROR': 'default', # ... plus other settings } }
  6. multiple databases DATABASES = { 'default': { 'HOST': 'dbprimary', #

    ... plus other settings }, 'replica': { 'HOST': 'dbreplica', 'TEST': { 'MIRROR': 'default', }, # ... plus other settings } }
  7. multiple databases DATABASES = { 'default': { 'HOST': 'dbprimary', #

    ... plus other settings }, 'replica': { 'HOST': 'dbreplica', 'TEST': { 'MIRROR': 'default', }, # ... plus other settings } }
  8. 1.3

  9. 1.4

  10. 1.5

  11. 1.6

  12. 1.7

  13. 1.8

  14. TestCase after enter atomic load fixtures enter atomic … exit

    atomic exit atomic close connections 1.8
  15. TestCase after enter atomic load fixtures enter atomic … exit

    atomic exit atomic close connections { once 1.8
  16. TestCase after enter atomic load fixtures enter atomic … exit

    atomic exit atomic close connections } times # of tests { once 1.8
  17. 1.9

  18. 1.9

  19. # Starting with Python 3.4 def test_even(self): for i in

    range(0, 6): with self.subTest(i=i): self.assertEqual(i % 2, 0)
  20. # Starting with Python 3.4 def test_even(self): for i in

    range(0, 6): with self.subTest(i=i): self.assertEqual(i % 2, 0)
  21. # Starting with Python 3.4 def test_even(self): for i in

    range(0, 6): with self.subTest(i=i): self.assertEqual(i % 2, 0) --parallel
  22. original email backend 9 original test renderer delete state and

    mailbox self.teardown_test_environment()
  23. TransactionTestCase allows database queries access to test client fast allows

    database transactions flushes database after each test
  24. TestCase allows database queries access to test client faster restricts

    database transactions runs each test in a transaction
  25. from hypothesis import given from hypothesis.strategies import text @given(text()) def

    test_decode_inverts_encode(s): assert decode(encode(s)) == s
  26. from hypothesis import given from hypothesis.strategies import text @given(text()) def

    test_decode_inverts_encode(s): assert decode(encode(s)) == s
  27. from hypothesis import given from hypothesis.strategies import text @given(text()) def

    test_decode_inverts_encode(s): assert decode(encode(s)) == s
  28. from hypothesis import given from hypothesis.strategies import text @given(text()) def

    test_decode_inverts_encode(s): assert decode(encode(s)) == s
  29. from hypothesis import given from hypothesis.strategies import text @given(text()) def

    test_decode_inverts_encode(s): assert decode(encode(s)) == s
  30. from hypothesis import given from hypothesis.strategies import text @given(text()) def

    test_decode_inverts_encode(s): assert decode(encode(s)) == s rerun for different values
  31. 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))
  32. 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))
  33. 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))
  34. 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))
  35. 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))
  36. 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))
  37. AOR - arithmetic operator replacement BCR - break continue replacement

    COI - conditional operator insertion CRP - constant replacement DDL - decorator deletion LOR - logical operator replacement
  38. [*] Start mutation process: - targets: django.utils.encoding - tests: tests.utils_tests.test_encoding

    [*] 10 tests passed: - tests.utils_tests.test_encoding [0.00533 s] [*] Start mutants generation and execution: ... [*] Mutation score [12.19066 s]: 32.1% - all: 88 - killed: 24 (27.3%) - survived: 55 (62.5%) - incompetent: 7 (8.0%) - timeout: 2 (2.3%)
  39. [*] Start mutation process: - targets: django.utils.encoding - tests: tests.utils_tests.test_encoding

    [*] 10 tests passed: - tests.utils_tests.test_encoding [0.00533 s] [*] Start mutants generation and execution: ... [*] Mutation score [12.19066 s]: 32.1% - all: 88 - killed: 24 (27.3%) - survived: 55 (62.5%) - incompetent: 7 (8.0%) - timeout: 2 (2.3%)
  40. 1. use MD5PasswordHasher 2. have more SimpleTestCase 3. use setUpTestData()

    4. use mocks EVERYWHERE 5. be vigilant of what gets created in setUp()
  41. 1. use MD5PasswordHasher 2. have more SimpleTestCase 3. use setUpTestData()

    4. use mocks EVERYWHERE 5. be vigilant of what gets created in setUp() 6. don’t save model objects if not necessary
  42. 1. use MD5PasswordHasher 2. have more SimpleTestCase 3. use setUpTestData()

    4. use mocks EVERYWHERE 5. be vigilant of what gets created in setUp() 6. don’t save model objects if not necessary 7. isolate unit tests
  43. 1. use MD5PasswordHasher 2. have more SimpleTestCase 3. use setUpTestData()

    4. use mocks EVERYWHERE 5. be vigilant of what gets created in setUp() 6. don’t save model objects if not necessary 7. isolate unit tests
  44. 6. don’t save model objects if not necessary Robot.objects.create() Robot()

    RobotFactory.build() RobotFactory.stub() instead of maybe do or or
  45. 6. don’t save model objects if not necessary Robot.objects.create() Robot()

    RobotFactory.build() RobotFactory.stub() factory boy instead of maybe do or or }
  46. class ProductQuestionnaireCreate(CreateView): def form_valid(self, form): if is_biscuit and is_coated_in_chocolate: set_vat_20()

    return super().form_valid(form) class ProductQuestionnaireCreateTestCase(TestCase): def test_20p_vat_if_coated_in_chocolate_biscuit(self): product = ProductFactory() response = self.client.post(self.url, {'q1': 'a1', 'q2': 'a2'}) product.refresh_from_db() self.assertEqual(product.vat, 20) solution #1
  47. class ProductQuestionnaireCreate(CreateView): def form_valid(self, form): if is_biscuit and is_coated_in_chocolate: set_vat_20()

    return super().form_valid(form) class ProductQuestionnaireCreateTestCase(TestCase): def test_20p_vat_if_coated_in_chocolate_biscuit(self): product = ProductFactory() response = self.client.post(self.url, {'q1': 'a1', 'q2': 'a2'}) product.refresh_from_db() self.assertEqual(product.vat, 20) solution #1
  48. To test if I need to pay 20% VAT for

    biscuits coated in chocolate, I need to: go through the router interact with database send input to receive output
  49. class ProductQuestionnaireForm(forms.ModelForm): def save(self, commit=True): instance = super().save(commit) if is_biscuit

    and is_coated_in_chocolate: set_vat_20() return instance solution #2 class ProductQuestionnaireFormTestCase(TestCase): def test_20p_vat_if_coated_in_chocolate_biscuit(self): product = ProductFactory() form = ProductQuestionnaireForm(data={'k1': 'v1', 'k2': 'v2'}) self.assertTrue(form.is_valid()) form.save() product.refresh_from_db() self.assertEqual(product.vat, 20)
  50. class ProductQuestionnaireForm(forms.ModelForm): def save(self, commit=True): instance = super().save(commit) if is_biscuit

    and is_coated_in_chocolate: set_vat_20() return instance solution #2 class ProductQuestionnaireFormTestCase(TestCase): def test_20p_vat_if_coated_in_chocolate_biscuit(self): product = ProductFactory() form = ProductQuestionnaireForm(data={'k1': 'v1', 'k2': 'v2'}) self.assertTrue(form.is_valid()) form.save() product.refresh_from_db() self.assertEqual(product.vat, 20)
  51. To test if I need to pay 20% VAT for

    biscuits coated in chocolate, I need to: go through the router interact with database send input to receive output
  52. solution #3 class VATCalculator(object): def calculate_vat(self, **kwargs): if is_biscuit and

    is_coated_in_chocolate: return 20 class VATCalculatorTestCase(SimpleTestCase): def test_20p_vat_if_coated_in_chocolate_biscuit(self): calc = VATCalculator() self.assertEqual(calc.calculate_vat( is_biscuit=True, is_coated_in_choco=True ), 20)
  53. To test if I need to pay 20% VAT for

    biscuits coated in chocolate, I need to: go through the router interact with database send input to receive output