Slide 1

Slide 1 text

TESTING in DJANGO by Ana Balica

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

@anabalica

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Questions?

Slide 6

Slide 6 text

Django archeology 1.0

Slide 7

Slide 7 text

Django archeology 1.0 1.11 1.1 1.10 …

Slide 8

Slide 8 text

#2333 Add unit test framework for end-user Django applications

Slide 9

Slide 9 text

#2333 As an added incentive, this is a feature that is present in Rails. Add unit test framework for end-user Django applications ”

Slide 10

Slide 10 text

./manage.py test 1.0

Slide 11

Slide 11 text

./manage.py test app.TestClass.test_method 1.0

Slide 12

Slide 12 text

TEST RUNNER 1.0 setup test environment tests.py models.py teardown & results

Slide 13

Slide 13 text

Client 1.0 get post login logout

Slide 14

Slide 14 text

testcase 1.0 1.0 assert* Redirects (Not)Contains FormError Template(Not)Used

Slide 15

Slide 15 text

1.1

Slide 16

Slide 16 text

Client 1.1 put head delete options

Slide 17

Slide 17 text

TestCase 1.1 TransactionTestCase

Slide 18

Slide 18 text

1.1 my test enter transaction rollback transaction I am a TestCase burger

Slide 19

Slide 19 text

1.1 I am a TransactionTestCase. I flush the database before each test.

Slide 20

Slide 20 text

1.2

Slide 21

Slide 21 text

DjangoTestSuiteRunner from function to class

Slide 22

Slide 22 text

1.2 failures = test_runner(test_labels, verbosity, interactive) if failures: sys.exit(failures)

Slide 23

Slide 23 text

1.2 failures = test_runner(test_labels, verbosity, interactive) if failures: sys.exit(failures)

Slide 24

Slide 24 text

1.2 0 success failures = test_runner(test_labels, verbosity, interactive) if failures: sys.exit(failures)

Slide 25

Slide 25 text

1.2 1 failure failures = test_runner(test_labels, verbosity, interactive) if failures: sys.exit(failures)

Slide 26

Slide 26 text

1.2 42 failure failures = test_runner(test_labels, verbosity, interactive) if failures: sys.exit(failures)

Slide 27

Slide 27 text

1.2 256 success failures = test_runner(test_labels, verbosity, interactive) if failures: sys.exit(failures)

Slide 28

Slide 28 text

1.2 failures = TestRunner(verbosity, interactive, failfast) if failures: sys.exit(1)

Slide 29

Slide 29 text

1.2 failures = TestRunner(verbosity, interactive, failfast) if failures: sys.exit(1)

Slide 30

Slide 30 text

1.2 0 / 1 success failure

Slide 31

Slide 31 text

failures = TestRunner(verbosity, interactive, failfast) if failures: sys.exit(1) 1.2 256 failure

Slide 32

Slide 32 text

failures = TestRunner(verbosity, interactive, failfast) if failures: sys.exit(1) 1.2 256 failure

Slide 33

Slide 33 text

failures = TestRunner(verbosity, interactive, failfast) if failures: sys.exit(1) 1.2 256 failure

Slide 34

Slide 34 text

failures = TestRunner(verbosity, interactive, failfast) if failures: sys.exit(1) 1.2 256 failure

Slide 35

Slide 35 text

failures = TestRunner(verbosity, interactive, failfast) if failures: sys.exit(1) 1.2 256 failure

Slide 36

Slide 36 text

1.2 multiple databases

Slide 37

Slide 37 text

1.2 multiple databases

Slide 38

Slide 38 text

1.2 multiple databases primary replica

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

1.2 multiple databases primary replica

Slide 44

Slide 44 text

1.2 multiple databases primary replica

Slide 45

Slide 45 text

1.2 multiple databases primary replica

Slide 46

Slide 46 text

1.3

Slide 47

Slide 47 text

testcase 1.0 1.3 assert* QuerysetEqual NumQueries

Slide 48

Slide 48 text

1.3 RequestFactory Client

Slide 49

Slide 49 text

1.3 class RequestFactory(object): def request(self, **request): return WSGIRequest(self._base_environ(**request))

Slide 50

Slide 50 text

1.3 doctests = tests + documentation

Slide 51

Slide 51 text

1.3 doctests = tests + documentation

Slide 52

Slide 52 text

1.4

Slide 53

Slide 53 text

1.4 Transaction TestCase TestCase

Slide 54

Slide 54 text

1.4 Transaction TestCase TestCase Simple TestCase LiveServer TestCase

Slide 55

Slide 55 text

1.4 Transaction TestCase TestCase Simple TestCase LiveServer TestCase doesn’t hit the database

Slide 56

Slide 56 text

1.4 Transaction TestCase TestCase Simple TestCase LiveServer TestCase doesn’t hit the database runs an http server

Slide 57

Slide 57 text

1.4 Transaction TestCase TestCase Simple TestCase LiveServer TestCase doesn’t hit the database runs an http server

Slide 58

Slide 58 text

1.5

Slide 59

Slide 59 text

1.5 I am a TransactionTestCase. I flush the database before each test.

Slide 60

Slide 60 text

1.5 I am a TransactionTestCase. I flush the database before each test. after

Slide 61

Slide 61 text

1.5 flush run TransactionTestCase enter transaction run TestCase rollback transaction {

Slide 62

Slide 62 text

1.5 flush run TransactionTestCase enter transaction run TestCase rollback transaction { dirty state

Slide 63

Slide 63 text

1.5 flush {run TransactionTestCase enter transaction run TestCase rollback transaction

Slide 64

Slide 64 text

1.5 flush { run TransactionTestCase enter transaction run TestCase rollback transaction

Slide 65

Slide 65 text

1.5 flush {run TransactionTestCase enter transaction run TestCase rollback transaction

Slide 66

Slide 66 text

1.5 flush {run TransactionTestCase enter transaction run TestCase rollback transaction

Slide 67

Slide 67 text

1.5 flush {run TransactionTestCase enter transaction run TestCase rollback transaction problem solved

Slide 68

Slide 68 text

1.6

Slide 69

Slide 69 text

Client 1.6 patch

Slide 70

Slide 70 text

1.6 TEST_RUNNER improvements Test discovery Full paths vs pseudo paths Doctests discovery

Slide 71

Slide 71 text

1.6 TEST_RUNNER improvements Test discovery Full paths vs pseudo paths Doctests discovery

Slide 72

Slide 72 text

1.6 TEST_RUNNER improvements Test discovery Full paths vs pseudo paths Doctests discovery

Slide 73

Slide 73 text

1.7

Slide 74

Slide 74 text

1.7 unittest2

Slide 75

Slide 75 text

1.7 unittest2 unittest

Slide 76

Slide 76 text

1.7 LiveServerTestCase StaticLiveServerTestCase

Slide 77

Slide 77 text

1.8

Slide 78

Slide 78 text

Client 1.8 trace

Slide 79

Slide 79 text

TestCase before enter atomic load fixtures … exit atomic close connections 1.8

Slide 80

Slide 80 text

TestCase before enter atomic load fixtures … exit atomic close connections 1.8

Slide 81

Slide 81 text

TestCase before enter atomic load fixtures … exit atomic close connections } times # of tests 1.8

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

TestCase after enter atomic load fixtures enter atomic … exit atomic exit atomic close connections } times # of tests { once 1.8

Slide 85

Slide 85 text

1.9

Slide 86

Slide 86 text

1.9 --parallel

Slide 87

Slide 87 text

1.9

Slide 88

Slide 88 text

1.9 workers

Slide 89

Slide 89 text

1.9 workers databases

Slide 90

Slide 90 text

1.9 workers databases suite

Slide 91

Slide 91 text

1.9 workers databases partitions

Slide 92

Slide 92 text

1.9 workers databases partitions

Slide 93

Slide 93 text

1.9 workers databases partitions

Slide 94

Slide 94 text

1.9 workers databases partitions

Slide 95

Slide 95 text

1.9 workers databases partitions

Slide 96

Slide 96 text

1.9 workers databases partitions

Slide 97

Slide 97 text

1.9 workers databases partitions

Slide 98

Slide 98 text

nose multiprocess plugin

Slide 99

Slide 99 text

1.10

Slide 100

Slide 100 text

1.10 class SampleTestCase(TestCase): @tag('slow') def test_slow(self): ...

Slide 101

Slide 101 text

1.10 ./manage.py test --tag=slow class SampleTestCase(TestCase): @tag('slow') def test_slow(self): ...

Slide 102

Slide 102 text

1.10 ./manage.py test --tag=slow ./manage.py test --exclude-tag=slow class SampleTestCase(TestCase): @tag('slow') def test_slow(self): ...

Slide 103

Slide 103 text

nose attrib plugin py.test markers

Slide 104

Slide 104 text

nose attrib plugin py.test markers will do the same

Slide 105

Slide 105 text

1.11

Slide 106

Slide 106 text

# 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)

Slide 107

Slide 107 text

# 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)

Slide 108

Slide 108 text

# 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

Slide 109

Slide 109 text

TEST bed or what happens when you run ./manage.py test

Slide 110

Slide 110 text

No content

Slide 111

Slide 111 text

No content

Slide 112

Slide 112 text

$ ./manage.py test 1

Slide 113

Slide 113 text

TestRunner = get_runner(settings, options['testrunner']) test_runner = TestRunner(**options) failures = test_runner.run_tests(test_labels) ../management/commands/test.py 2

Slide 114

Slide 114 text

TestRunner = get_runner(settings, options['testrunner']) test_runner = TestRunner(**options) failures = test_runner.run_tests(test_labels) ../management/commands/test.py 2

Slide 115

Slide 115 text

3 self.setup_test_environment()

Slide 116

Slide 116 text

3 self.setup_test_environment() locmem email backend

Slide 117

Slide 117 text

3 self.setup_test_environment() locmem email backend instrumented test renderer

Slide 118

Slide 118 text

3 self.setup_test_environment() locmem email backend instrumented test renderer deactivate translations

Slide 119

Slide 119 text

4 self.build_suite(test_labels, extra_tests)

Slide 120

Slide 120 text

4 self.build_suite(test_labels, extra_tests)

Slide 121

Slide 121 text

5 self.setup_databases() 7 self.run_suite(suite) 8 self.teardown_databases(old_config) 6 self.run_checks()

Slide 122

Slide 122 text

5 self.setup_databases() 7 self.run_suite(suite) 8 self.teardown_databases(old_config) 6 self.run_checks()

Slide 123

Slide 123 text

5 self.setup_databases() 7 self.run_suite(suite) 8 self.teardown_databases(old_config) 6 self.run_checks()

Slide 124

Slide 124 text

5 self.setup_databases() 7 self.run_suite(suite) 8 self.teardown_databases(old_config) 6 self.run_checks()

Slide 125

Slide 125 text

9 self.teardown_test_environment()

Slide 126

Slide 126 text

original email backend 9 self.teardown_test_environment()

Slide 127

Slide 127 text

original email backend 9 original test renderer self.teardown_test_environment()

Slide 128

Slide 128 text

original email backend 9 original test renderer delete state and mailbox self.teardown_test_environment()

Slide 129

Slide 129 text

10 self.suite_result(suite, result) len(result.failures) + len(result.errors) if failures: sys.exit(1)

Slide 130

Slide 130 text

test classes

Slide 131

Slide 131 text

SimpleTestCase TransactionTestCase TestCase LiveServerTestCase StaticLiveServerTestCase

Slide 132

Slide 132 text

SimpleTestCase no database queries access to test client fast

Slide 133

Slide 133 text

TransactionTestCase allows database queries access to test client fast allows database transactions flushes database after each test

Slide 134

Slide 134 text

TestCase allows database queries access to test client faster restricts database transactions runs each test in a transaction

Slide 135

Slide 135 text

LiveServerTestCase acts like TransactionTestCase launches a live HTTP server in a separate thread

Slide 136

Slide 136 text

StaticLiveServerTestCase acts like TransactionTestCase launches a live HTTP server in a separate thread serves static files

Slide 137

Slide 137 text

Client

Slide 138

Slide 138 text

Client RequestFactory ClientHandler

Slide 139

Slide 139 text

Client RequestFactory ClientHandler constructs requests encodes data

Slide 140

Slide 140 text

Client RequestFactory ClientHandler constructs requests encodes data

Slide 141

Slide 141 text

Client RequestFactory ClientHandler constructs requests encodes data

Slide 142

Slide 142 text

Client RequestFactory ClientHandler stateful response++ handles redirects constructs requests encodes data

Slide 143

Slide 143 text

Client RequestFactory ClientHandler stateful response++ handles redirects constructs requests encodes data

Slide 144

Slide 144 text

Client RequestFactory ClientHandler stateful response++ handles redirects constructs requests encodes data

Slide 145

Slide 145 text

Client RequestFactory ClientHandler stateful response++ handles redirects constructs requests encodes data

Slide 146

Slide 146 text

Client RequestFactory ClientHandler stateful response++ handles redirects loads middleware emulates disables CSRF constructs requests encodes data

Slide 147

Slide 147 text

Client RequestFactory ClientHandler stateful response++ handles redirects loads middleware emulates disables CSRF constructs requests encodes data

Slide 148

Slide 148 text

Client RequestFactory ClientHandler stateful response++ handles redirects loads middleware emulates disables CSRF constructs requests encodes data

Slide 149

Slide 149 text

Client RequestFactory ClientHandler stateful response++ handles redirects loads middleware emulates disables CSRF constructs requests encodes data

Slide 150

Slide 150 text

Client RequestFactory ClientHandler stateful response++ handles redirects loads middleware emulates disables CSRF constructs requests encodes data

Slide 151

Slide 151 text

Quality

Slide 152

Slide 152 text

Factory Boy fixtures replacement with random and realistic values

Slide 153

Slide 153 text

Factory Boy fixtures replacement with random and realistic values generated by Faker

Slide 154

Slide 154 text

property based testing with Hypothesis

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

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

Slide 160

Slide 160 text

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

Slide 161

Slide 161 text

from hypothesis.extra.django.models import models from hypothesis.strategies import integers models(Customer).example() models(Customer, age=integers( min_value=0, max_value=120) ).example()

Slide 162

Slide 162 text

from hypothesis.extra.django.models import models from hypothesis.strategies import integers models(Customer).example() models(Customer, age=integers( min_value=0, max_value=120) ).example()

Slide 163

Slide 163 text

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))

Slide 164

Slide 164 text

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))

Slide 165

Slide 165 text

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))

Slide 166

Slide 166 text

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))

Slide 167

Slide 167 text

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))

Slide 168

Slide 168 text

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))

Slide 169

Slide 169 text

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

Slide 170

Slide 170 text

Property based testing is more complicated, yet more valuable

Slide 171

Slide 171 text

Django test code coverage is 75%

Slide 172

Slide 172 text

Deceptive metric

Slide 173

Slide 173 text

High coverage != high quality

Slide 174

Slide 174 text

Mutation testing available Python implementation: mutpy

Slide 175

Slide 175 text

mut.py --target node --unit-test test_node target unit test

Slide 176

Slide 176 text

if foo and bar: do_this() target unit test

Slide 177

Slide 177 text

if foo and bar: do_this() if foo or bar: do_this() mutant unit test

Slide 178

Slide 178 text

mutant killed

Slide 179

Slide 179 text

mutant killed mutant survived

Slide 180

Slide 180 text

AOR - arithmetic operator replacement BCR - break continue replacement COI - conditional operator insertion CRP - constant replacement DDL - decorator deletion LOR - logical operator replacement

Slide 181

Slide 181 text

[*] 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%)

Slide 182

Slide 182 text

[*] 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%)

Slide 183

Slide 183 text

Django duration utils mutation score is 89%

Slide 184

Slide 184 text

Django duration utils mutation score is 89% and the coverage is 100%

Slide 185

Slide 185 text

Django encoding utils mutation score is 64%

Slide 186

Slide 186 text

Django encoding utils mutation score is 64% while the coverage is 100%

Slide 187

Slide 187 text

django Testing tutorial

Slide 188

Slide 188 text

my_app ├── __init__.py ├── admin.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.py

Slide 189

Slide 189 text

my_app ├── __init__.py ├── admin.py ├── migrations │ └── __init__.py ├── models.py ├── tests.py └── views.py

Slide 190

Slide 190 text

When testing, more is better

Slide 191

Slide 191 text

# of tests total execution time

Slide 192

Slide 192 text

slow tests, slow feedback loop

Slide 193

Slide 193 text

7 tips on how to speed up your tests

Slide 194

Slide 194 text

7 tips on how to speed up your tests #4 will shock you

Slide 195

Slide 195 text

No content

Slide 196

Slide 196 text

1. use MD5PasswordHasher

Slide 197

Slide 197 text

1. use MD5PasswordHasher 2. have more SimpleTestCase

Slide 198

Slide 198 text

1. use MD5PasswordHasher 2. have more SimpleTestCase 3. use setUpTestData()

Slide 199

Slide 199 text

1. use MD5PasswordHasher 2. have more SimpleTestCase 3. use setUpTestData() 4. use mocks EVERYWHERE

Slide 200

Slide 200 text

1. use MD5PasswordHasher 2. have more SimpleTestCase 3. use setUpTestData() 4. use mocks EVERYWHERE

Slide 201

Slide 201 text

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

Slide 202

Slide 202 text

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

Slide 203

Slide 203 text

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

Slide 204

Slide 204 text

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

Slide 205

Slide 205 text

class SimpleTest(TestCase): def setUp(self): for _ in range(10): Robot.objects.create() 5. be vigilant of what gets created in setUp()

Slide 206

Slide 206 text

class SimpleTest(TestCase): def setUp(self): for _ in range(10): Robot.objects.create() 5. be vigilant of what gets created in setUp()

Slide 207

Slide 207 text

6. don’t save model objects if not necessary

Slide 208

Slide 208 text

6. don’t save model objects if not necessary Robot.objects.create() instead of

Slide 209

Slide 209 text

6. don’t save model objects if not necessary Robot.objects.create() Robot() instead of maybe do

Slide 210

Slide 210 text

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

Slide 211

Slide 211 text

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

Slide 212

Slide 212 text

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 }

Slide 213

Slide 213 text

7. isolate unit tests

Slide 214

Slide 214 text

7. isolate unit tests

Slide 215

Slide 215 text

7. isolate unit tests

Slide 216

Slide 216 text

7. isolate unit tests unit tests functional tests

Slide 217

Slide 217 text

7. isolate unit tests unit tests functional tests ./manage.py test --tag=unit

Slide 218

Slide 218 text

No content

Slide 219

Slide 219 text

Ugh, taxes!

Slide 220

Slide 220 text

Product ProductQuestionnaire name ingredients price product category drink_category food_category has_decorations is_coated_in_chocolate is_warm is_cold

Slide 221

Slide 221 text

No content

Slide 222

Slide 222 text

Production code Test

Slide 223

Slide 223 text

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

Slide 224

Slide 224 text

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

Slide 225

Slide 225 text

class ProductQuestionnaireCreate(CreateView): def form_valid(self, form): return super().form_valid(form)

Slide 226

Slide 226 text

class ProductQuestionnaireCreateTestCase(TestCase): def test_20p_vat_if_coated_in_chocolate_biscuit(self): def test_0p_vat_if_baguette(self): def test_0p_vat_if_flapjack(self): def test_20p_vat_if_cereal_bar(self):

Slide 227

Slide 227 text

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

Slide 228

Slide 228 text

Mocks are not a solution

Slide 229

Slide 229 text

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)

Slide 230

Slide 230 text

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)

Slide 231

Slide 231 text

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

Slide 232

Slide 232 text

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)

Slide 233

Slide 233 text

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

Slide 234

Slide 234 text

reusability testability extensibility

Slide 235

Slide 235 text

No content

Slide 236

Slide 236 text

Tests have more in them than we think

Slide 237

Slide 237 text

Happy testing and big bear hug thanks!