@@ -226,7 +226,6 @@ def test_uses_home_template(self):
226
226
response = self.client.get("/")
227
227
self.assertTemplateUsed(response, "home.html")
228
228
229
-
230
229
def test_can_save_a_POST_request(self):
231
230
response = self.client.post("/", data={"item_text": "A new list item"})
232
231
self.assertIn("A new list item", response.content.decode())
@@ -575,31 +574,36 @@ begin to see that our first cut solution really isn't going to, um, cut it:
575
574
----
576
575
====
577
576
578
- ((("", startref="DTtemplate05"))) Sure
579
- enough, the functional tests return an error:
577
+ ((("", startref="DTtemplate05")))
578
+ Sure enough, the functional tests return an error:
580
579
581
580
----
582
581
AssertionError: '1: Buy peacock feathers' not found in ['1: Use peacock
583
582
feathers to make a fly']
584
583
----
585
584
585
+
586
586
Three Strikes and Refactor
587
587
~~~~~~~~~~~~~~~~~~~~~~~~~~
588
588
589
589
590
- ((("code smell")))((("database testing", "three strikes and refactor rule", id="DTthree05")))((("three strikes and refactor rule", id="threestrikes05")))((("refactoring", id="refactor05"))) Before
591
- we go further-- we've got a bad
590
+ ((("code smell")))
591
+ ((("database testing", "three strikes and refactor rule", id="DTthree05")))
592
+ ((("three strikes and refactor rule", id="threestrikes05")))
593
+ ((("refactoring", id="refactor05")))
594
+ Before we go further-- we've got a bad
592
595
__code smell__ footnote:[If you've not come across the concept, a "code smell" is
593
596
something about a piece of code that makes you want to rewrite it. Jeff Atwood
594
597
has http://www.codinghorror.com/blog/2006/05/code-smells.html[a compilation on
595
598
his blog Coding Horror]. The more experience you gain as a programmer, the more
596
599
fine-tuned your nose becomes to code smells... ]
597
- in this FT. We have three
598
- almost identical code blocks checking for new items in the list table. ((("Don’t Repeat Yourself (DRY)"))) There's
599
- a principle called 'Don't Repeat Yourself' (DRY), which we like to apply by
600
- following the mantra 'three strikes and refactor' . You can copy and paste code
601
- once, and it may be premature to try to remove the duplication it causes, but
602
- once you get three occurrences, it's time to remove duplication.
600
+ in this FT.
601
+ We have three almost identical code blocks checking for new items in the list table.
602
+ ((("Don’t Repeat Yourself (DRY)"))) There's a principle called 'Don't Repeat Yourself' (DRY),
603
+ which we like to apply by following the mantra 'three strikes and refactor' .
604
+ You can copy and paste code once,
605
+ and it may be premature to try to remove the duplication it causes,
606
+ but once you get three occurrences, it's time to remove duplication.
603
607
604
608
605
609
@@ -888,7 +892,7 @@ The test actually gets surprisingly far:
888
892
----
889
893
$ pass:quotes[*python manage.py test lists*]
890
894
[...]
891
- self.assertEqual(first_saved_item.text, ' The first (ever) list item' )
895
+ self.assertEqual(first_saved_item.text, " The first (ever) list item" )
892
896
AttributeError: 'Item' object has no attribute 'text'
893
897
----
894
898
@@ -1027,7 +1031,7 @@ response. We can do that by adding three new lines to the existing test called
1027
1031
1028
1032
1029
1033
[role="sourcecode"]
1030
- .lists/tests.py
1034
+ .lists/tests.py (ch05l019)
1031
1035
====
1032
1036
[source,python]
1033
1037
----
@@ -1152,7 +1156,7 @@ class HomePageTest(TestCase):
1152
1156
[...]
1153
1157
1154
1158
def test_only_saves_items_when_necessary(self):
1155
- self.client.get('/' )
1159
+ self.client.get("/" )
1156
1160
self.assertEqual(Item.objects.count(), 0)
1157
1161
----
1158
1162
====
@@ -1162,10 +1166,13 @@ quite a small change to the logic of the view, there are quite a few little
1162
1166
tweaks to the implementation in code:
1163
1167
1164
1168
[role="sourcecode"]
1165
- .lists/views.py (ch05l023)
1169
+ .lists/views.py
1166
1170
====
1167
1171
[source,python]
1168
1172
----
1173
+ from lists.models import Item
1174
+ [...]
1175
+
1169
1176
def home_page(request):
1170
1177
if request.method == "POST":
1171
1178
new_item_text = request.POST["item_text"] # <1>
@@ -1220,14 +1227,14 @@ the item in it, it should redirect back to the home page:
1220
1227
[source,python]
1221
1228
----
1222
1229
def test_can_save_a_POST_request(self):
1223
- response = self.client.post('/' , data={' item_text': ' A new list item' })
1230
+ response = self.client.post("/" , data={" item_text": " A new list item" })
1224
1231
1225
1232
self.assertEqual(Item.objects.count(), 1)
1226
1233
new_item = Item.objects.first()
1227
- self.assertEqual(new_item.text, ' A new list item' )
1234
+ self.assertEqual(new_item.text, " A new list item" )
1228
1235
1229
1236
self.assertEqual(response.status_code, 302)
1230
- self.assertEqual(response[' location' ], '/' )
1237
+ self.assertEqual(response[" location" ], "/" )
1231
1238
----
1232
1239
====
1233
1240
@@ -1291,17 +1298,16 @@ first go, but now feels like a good time to separate out our concerns:
1291
1298
[source,python]
1292
1299
----
1293
1300
def test_can_save_a_POST_request(self):
1294
- self.client.post('/' , data={' item_text': ' A new list item' })
1301
+ self.client.post("/" , data={" item_text": " A new list item" })
1295
1302
1296
1303
self.assertEqual(Item.objects.count(), 1)
1297
1304
new_item = Item.objects.first()
1298
- self.assertEqual(new_item.text, 'A new list item')
1299
-
1305
+ self.assertEqual(new_item.text, "A new list item")
1300
1306
1301
1307
def test_redirects_after_POST(self):
1302
- response = self.client.post('/' , data={' item_text': ' A new list item' })
1308
+ response = self.client.post("/" , data={" item_text": " A new list item" })
1303
1309
self.assertEqual(response.status_code, 302)
1304
- self.assertEqual(response[' location' ], '/' )
1310
+ self.assertEqual(response[" location" ], "/" )
1305
1311
----
1306
1312
====
1307
1313
@@ -1346,13 +1352,13 @@ class HomePageTest(TestCase):
1346
1352
[...]
1347
1353
1348
1354
def test_displays_all_list_items(self):
1349
- Item.objects.create(text=' itemey 1' )
1350
- Item.objects.create(text=' itemey 2' )
1355
+ Item.objects.create(text=" itemey 1" )
1356
+ Item.objects.create(text=" itemey 2" )
1351
1357
1352
- response = self.client.get('/' )
1358
+ response = self.client.get("/" )
1353
1359
1354
- self.assertIn(' itemey 1' , response.content.decode())
1355
- self.assertIn(' itemey 2' , response.content.decode())
1360
+ self.assertIn(" itemey 1" , response.content.decode())
1361
+ self.assertIn(" itemey 2" , response.content.decode())
1356
1362
----
1357
1363
====
1358
1364
@@ -1361,7 +1367,7 @@ NOTE: Are you wondering about the line spacing in the test? I'm grouping
1361
1367
together two lines at the beginning which set up the test, one line in
1362
1368
the middle which actually calls the code under test, and the
1363
1369
assertions at the end. This isn't obligatory, but it does help see the
1364
- structure of the test. Setup, Exercise, Assert is the typical structure
1370
+ structure of the test. Arrange-Act- Assert is the typical structure
1365
1371
for a unit test.
1366
1372
1367
1373
@@ -1405,12 +1411,12 @@ pass the items to it from our home page view:
1405
1411
[source,python]
1406
1412
----
1407
1413
def home_page(request):
1408
- if request.method == ' POST' :
1409
- Item.objects.create(text=request.POST[' item_text' ])
1410
- return redirect('/' )
1414
+ if request.method == " POST" :
1415
+ Item.objects.create(text=request.POST[" item_text" ])
1416
+ return redirect("/" )
1411
1417
1412
1418
items = Item.objects.all()
1413
- return render(request, ' home.html' , {' items' : items})
1419
+ return render(request, " home.html" , {" items" : items})
1414
1420
----
1415
1421
====
1416
1422
@@ -1465,9 +1471,9 @@ project directory:
1465
1471
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
1466
1472
1467
1473
DATABASES = {
1468
- ' default' : {
1469
- ' ENGINE': ' django.db.backends.sqlite3' ,
1470
- ' NAME' : BASE_DIR / ' db.sqlite3' ,
1474
+ " default" : {
1475
+ " ENGINE": " django.db.backends.sqlite3" ,
1476
+ " NAME" : BASE_DIR / " db.sqlite3" ,
1471
1477
}
1472
1478
}
1473
1479
----
@@ -1543,7 +1549,7 @@ Django template tag, `forloop.counter`, will help here:
1543
1549
If you try it again, you should now see the FT get to the end:
1544
1550
1545
1551
----
1546
- self.fail(' Finish the test!' )
1552
+ self.fail(" Finish the test!" )
1547
1553
AssertionError: Finish the test!
1548
1554
----
1549
1555
0 commit comments