Skip to content

Commit 114ff86

Browse files
committed
process todos up to middle of 4
1 parent b91d091 commit 114ff86

5 files changed

+64
-104
lines changed

appendix_purist_unit_tests.asciidoc

+3-3
Original file line numberDiff line numberDiff line change
@@ -374,8 +374,8 @@ for it to pass. We've started trying to make it more isolated, so let's now go
374374
all the way.
375375

376376

377-
Keep the Old Integrated Test Suite Around as a Sanity Check
378-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
377+
Keep the Old Integrated Test Suite Around as a Sense Check
378+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
379379

380380
Let's rename our old `NewListTest` class to `NewListViewIntegratedTest`,
381381
and throw away our attempt at a mocky test for saving the owner, putting
@@ -1864,7 +1864,7 @@ integration problems. That's perfectly valid.
18641864

18651865
On the other hand, we saw how integrated tests can warn you when you've made
18661866
small mistakes in integrating your layers. We could keep just a couple of
1867-
tests around as "sanity checks", to give us a quicker feedback cycle.
1867+
tests around as "sense checks", to give us a quicker feedback cycle.
18681868

18691869
How about these three:
18701870

chapter_02_unittest.asciidoc

-7
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,6 @@ which you can get a taste of by reading
166166
his https://web.stanford.edu/~ouster/cgi-bin/cs190-spring16/lecture.php?topic=comments[lecture notes from the chapter on comments.]
167167
*******************************************************************************
168168

169-
// CSANAD: In my opinion, this would be a good place for the second commit, before we change it to use the unittest module.
170-
//
171-
// edit:
172-
// Actually, from the diff of the commit at the end of this chapter, it is clear that a commit has been made after
173-
// adding the user story. But it isn't mentioned in the chapter.
174-
175-
176169
You'll notice that, apart from writing the test out as comments,
177170
I've updated the `assert` to look for the word ``To-Do'' instead of Django's ``Congratulations''.
178171
That means we expect the test to fail now. Let's try running it.

chapter_03_unit_test_first_view.asciidoc

+44-83
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,22 @@ so do stick it out!
4545
4646
((("Test-Driven Development (TDD)", "additional resources")))
4747
((("getting help")))
48-
You can always drop me an email (or try the
49-
https://groups.google.com/forum/#!forum/obey-the-testing-goat-book[Google
50-
Group]) if you get really stuck. Happy debugging!
48+
You can always drop me an email[mailto:[email protected]]
49+
if you get really stuck. Happy debugging!
5150
*******************************************************************************
5251

5352

5453

55-
5654
=== Our First Django App, and Our First Unit Test
5755

5856
((("Django framework", "code structure in")))
5957
((("Django framework", "unit testing in", id="DJFunit03")))
60-
Django encourages you to structure your code into _apps_: the theory is that
61-
one project can have many apps, you can use third-party apps developed by other
62-
people, and you might even reuse one of your own apps in a different
63-
project...although I admit I've never actually managed it myself! Still, apps
64-
are a good way to keep your code organised.
65-
// CSANAD: I would probably mention explicitly that despite of rarely re-using
66-
// our own apps, we do regularly re-use apps written by other developers.
58+
Django encourages you to structure your code into _apps_:
59+
the theory is that one project can have many apps,
60+
you can use third-party apps developed by other people,
61+
and you might even reuse one of your own apps in a different project...although
62+
I have to say I've never actually managed the latter, myself!
63+
Still, apps are a good way to keep your code organised.
6764

6865
Let's start an app for our to-do lists:
6966

@@ -144,8 +141,8 @@ You can see that, all the way through,
144141
the functional tests are driving what development we do from a high level,
145142
while the unit tests drive what we do at a low level.
146143

147-
The functional tests don't aim to cover every single tiny detail of our
148-
app's behaviour, they are there to reassure us that everything is wired up correctly.
144+
The functional tests don't aim to cover every single tiny detail of our app's behaviour,
145+
they are there to reassure us that everything is wired up correctly.
149146
The unit tests are there to exhaustively check all the lower level details and corner cases.
150147

151148
NOTE: Functional tests should help you build an application that actually works,
@@ -172,10 +169,12 @@ Enough theory for now—let's see how it looks in practice.
172169
|Provides confidence that everything is wired together correctly, works end-to-end
173170
|Can exhaustively check permutations, details, edge cases
174171

175-
|===
172+
|Can warn about problems without telling you exactly what's wrong
173+
|Can point at exactly where the problem is
176174

177-
// DAVID: Could include something about how unit tests provide more 'signal' about what went wrong.
178-
// SEBASTIAN: How about adding also performance aspect? That Unit Tests will generally run faster thus provide feedback earlier?
175+
|Slow
176+
|Fast
177+
|===
179178

180179
=== Unit Testing in Django
181180

@@ -195,18 +194,19 @@ from django.test import TestCase
195194
====
196195

197196

198-
Django has helpfully suggested we use a special version of `TestCase`, which
199-
it provides. It's an augmented version of the standard `unittest.TestCase`,
200-
with some additional Django-specific features, which we'll discover over the
201-
next few chapters.
197+
Django has helpfully suggested we use a special version of `TestCase`, which it provides.
198+
It's an augmented version of the standard `unittest.TestCase`,
199+
with some additional Django-specific features,
200+
which we'll discover over the next few chapters.
202201

203-
You've already seen that the TDD cycle involves starting with a test that
204-
fails, then writing code to get it to pass. Well, before we can even get that
205-
far, we want to know that the unit test we're writing will definitely be
206-
run by our automated test runner, whatever it is. In the case of
207-
_functional_tests.py_, we're running it directly, but this file made by Django
208-
is a bit more like magic. So, just to make sure, let's make a deliberately
209-
silly failing test:
202+
You've already seen that the TDD cycle involves starting with a test that fails,
203+
then writing code to get it to pass.
204+
Well, before we can even get that far,
205+
we want to know that the unit test we're writing
206+
will definitely be run by our automated test runner, whatever it is.
207+
In the case of _functional_tests.py_ we're running it directly,
208+
but this file made by Django is a bit more like magic.
209+
So, just to make sure, let's make a deliberately silly failing test:
210210

211211
[role="sourcecode"]
212212
.lists/tests.py (ch03l002)
@@ -223,8 +223,8 @@ class SmokeTest(TestCase):
223223
====
224224

225225

226-
Now let's invoke this mysterious Django test runner. As usual, it's a
227-
_manage.py_ [keep-together]#command#:
226+
Now let's invoke this mysterious Django test runner.
227+
As usual, it's a _manage.py_ [keep-together]#command#:
228228

229229

230230
[subs="specialcharacters,macros"]
@@ -250,8 +250,8 @@ FAILED (failures=1)
250250
Destroying test database for alias 'default'...
251251
----
252252

253-
Excellent. The machinery seems to be working. This is a good point for a
254-
commit:
253+
Excellent. The machinery seems to be working.
254+
This is a good point for a commit:
255255

256256

257257
[subs="specialcharacters,quotes"]
@@ -293,13 +293,6 @@ Django's workflow goes something like this:
293293
the request (this is referred to as _resolving_ the URL).
294294
3. The view function processes the request and returns an HTTP _response_.
295295

296-
// CSANAD: below > "remember, a view function takes an HTTP request as input"
297-
// but it wasn't _explicitly_ mentioned here. We could add something like:
298-
//
299-
// "2. Django uses some rules to decide which _view_ function should deal with
300-
// the request, and passes the request as a parameter for the view fuction. This
301-
// is referred to as _resolving_ the URL."
302-
303296

304297
So, we want to test two things:
305298

@@ -315,12 +308,6 @@ Let's start with the first.
315308

316309
=== Unit Testing a View
317310

318-
// CSANAD: what do you think about sharing the thought process on how we find the specific classes to use?
319-
// eg.: "Now that we know we need to make a request, let's discover what Django has to
320-
// offer" then look up "http request" on docs.djangoproject.com, maybe mention the first link,
321-
// `topics/http` being a manual, and the second one is a reference where we can see what classes
322-
// Django has; what kind of attributes they have. We could point out the `content` before using it.
323-
324311
((("unit tests", "in Django", "unit testing a view", secondary-sortas="Django")))
325312
Open up _lists/tests.py_, and change our silly test to something like this:
326313

@@ -368,10 +355,9 @@ So, to test that:
368355
with the words "To-Do lists" in it--because
369356
that's what we specified in our functional test.
370357

371-
<5> And we can do a vague sanity check that it's valid html, by checking
358+
<5> And we can do a vague sense-check that it's valid html, by checking
372359
that it starts with an `<html>` tag which gets closed at the end.
373360

374-
// DAVID: 'sanity check' is seen by some people as ableist, might be worth removing the 'sanity'?
375361

376362
So, what do you think will happen when we run the tests?
377363

@@ -544,12 +530,6 @@ from django.http import HttpResponse
544530
def home_page(request):
545531
return HttpResponse()
546532
----
547-
// CSANAD: using `django.http.HttpResponse` feels like a bit of a jump to me from the atomic step-by-step
548-
// approach. Of course, creating a dummy class with a `content` would be an overkill, but I think
549-
// some explanation on how the HttpResponse looks like would be helpful. This, actually, could be
550-
// mentioned earlier, because how do we know what we would be looking for in the response is called
551-
// the `content`?
552-
// SEBASTIAN: HTTPResponse was in fact mentioned earlier
553533
====
554534

555535
* Tests again:
@@ -681,7 +661,7 @@ FAILED (failures=1)
681661
Looks like something isn't quite right. This is the reason we have functional
682662
tests!
683663

684-
Do you remember at the beginning of the chapter, we said we needed to do two things,
664+
Do you remember at the beginning of the chapter, we said we needed to do two things:
685665
firstly create a view function to produce responses for requests,
686666
and secondly tell the server which functions should respond to which URLs?
687667
Thanks to our FT, we have been reminded that we still need to do the second thing.
@@ -694,10 +674,6 @@ But we want to test more layers of the Django stack.
694674
Django, like most web frameworks, supplies a tool for doing just that, called the
695675
https://docs.djangoproject.com/en/5.2/topics/testing/tools/#the-test-client[Django Test Client].
696676

697-
// CSANAD: it might be a little confusing first why we add a _unit_ test, considering this second
698-
// test case is closer to a user's point of view. We could point out this to be one example
699-
// to the problem mentioned earlier - the line between these tests being a bit vague at times.
700-
701677
Let's see how to use it by adding a second, alternative test to our unit tests:
702678

703679
[role="sourcecode"]
@@ -706,7 +682,7 @@ Let's see how to use it by adding a second, alternative test to our unit tests:
706682
[source,python]
707683
----
708684
class HomePageTest(TestCase):
709-
def test_home_page_returns_correct_html(self):
685+
def test_home_page_returns_correct_html(self): <1>
710686
request = HttpRequest()
711687
response = home_page(request)
712688
html = response.content.decode("utf8")
@@ -715,16 +691,14 @@ class HomePageTest(TestCase):
715691
self.assertTrue(html.endswith("</html>"))
716692
717693
def test_home_page_returns_correct_html_2(self):
718-
response = self.client.get("/") # <1>
719-
self.assertContains(response, "<title>To-Do lists</title>") # <2>
694+
response = self.client.get("/") # <2>
695+
self.assertContains(response, "<title>To-Do lists</title>") # <3>
720696
----
721697
====
722698

723-
// DAVID: Minor point, but I originally read this as a second test class. Personally I would find it a nicer
724-
// reading experience if the test method we've already seen was replaced by a `...`. Though
725-
// it probably would need an explanation that it's a convention we're using.
699+
<1> This is our existing.
726700

727-
<1> We can access the tests client via `self.client`,
701+
<2> In our new test, we access the test client via `self.client`,
728702
which is available on any test that uses `django.test.TestCase`.
729703
It provides methods like `.get()` which simulate a browser making http requests,
730704
and take a URL as their first parameter.
@@ -815,16 +789,10 @@ of _our own application code_ which was involved with the problem. In this
815789
case it's all Django code, but we'll see plenty of examples of this fifth step
816790
later in the book.
817791

818-
Pulling it all together, we interpret the traceback as telling us that,
819-
when we tried to do our assertion on the content of the response,
820-
Django's test helpers failed saying that they could not do that, because
821-
the response is an HTML 404 "Not Found" error instead of a normal 200 OK response.
822-
// CSANAD: This sentence is a little too long, might be difficult to interpret. Maybe making it a list?
823-
//
824-
// Pulling it all together, we interpret the traceback as the following:
825-
// - when we tried to do our assertion on the content of the response for requesting the root URL `/`
826-
// - Django's test helpers failed saying that they could not do that
827-
// - because the response is an HTML 404 "Not Found" error instead of a normal 200 OK response.
792+
Pulling it all together, we interpret the traceback as telling us that:
793+
* When we tried to do our assertion on the content of the response...
794+
* Django's test helpers failed, saying that they could not do that...
795+
* Because the response is an HTML 404 "Not Found" error, instead of a normal 200 OK response.
828796

829797
In other words, Django isn't yet configured to respond to requests for the
830798
root URL ("/") of our site. Let's make that happen now.
@@ -869,13 +837,6 @@ urlpatterns = [
869837
----
870838
====
871839

872-
873-
WARNING: If your _urls.py_ looks different or if it mentions a function called
874-
`url()` instead of `path()`, it's because you've got the wrong version of
875-
Django. This book is written for Django v5. Take another look at
876-
the <<pre-requisites>> section and get the right version before you
877-
go any further.
878-
879840
As usual, lots of helpful comments and default suggestions from Django.
880841
In fact, that very first example is pretty much exactly what we want!
881842
Let's use that, with some minor changes.
@@ -902,8 +863,8 @@ urlpatterns = [
902863
<3> And we wire it up here, as a `path()` entry in the `urlpatterns` global.
903864
Django strips the leading slash from all urls,
904865
so `"/url/path/to"` becomes `"url/path/to"`
905-
and the base URL is just the empty string, `""`. So this config
906-
says, the "base url should point to our home page view"
866+
and the base URL is just the empty string, `""`.
867+
So this config says, the "base url should point to our home page view"
907868

908869
Now we can run our unit tests again, with *`python manage.py test`*:
909870

chapter_04_philosophy_and_refactoring.asciidoc

+15-9
Original file line numberDiff line numberDiff line change
@@ -307,12 +307,12 @@ $ *git commit -am "Functional test now checks we can input a to-do item"*
307307

308308

309309

310-
=== The ``Don't Test Constants'' Rule, and Templates to the Rescue
310+
=== The "Don't Test Constants" Rule, and Templates to the Rescue
311311

312312

313313
((("“Don’t Test Constants” rule", primary-sortas="Don’t Test Constants rule")))
314314
((("unit tests", "“Don’t Test Constants” rule", secondary-sortas="Don’t Test Constants rule")))
315-
Let's take a look at our unit tests, 'lists/tests.py'.
315+
Let's take a look at our unit tests, _lists/tests.py_.
316316
Currently we're looking for specific HTML strings,
317317
but that's not a particularly efficient way of testing HTML.
318318
In general, one of the rules of unit testing is 'Don't test constants',
@@ -343,12 +343,14 @@ It's not _quite_ that simple, since HTML is code after all,
343343
and we do want something to check that we've written code that works,
344344
but that's our FT's job, not the unit tests'.
345345

346-
In any case, mangling raw strings in Python
346+
So maybe "don't test constants" isn't the online guideline at play here,
347+
but in any case, mangling raw strings in Python
347348
really isn't a great way of dealing with HTML.
348349
There's a much better solution, which is to use templates.
349350
Quite apart from anything else,
350351
if we can keep HTML to one side in a file whose name ends in '.html',
351352
we'll get better syntax highlighting!
353+
352354
There are lots of Python templating frameworks out there,
353355
and Django has its own which works very well.
354356
Let's use that.
@@ -487,10 +489,6 @@ Another chance to analyse a traceback:
487489
<4> Finally, we look for the part of our own application code that caused the
488490
failure: it's when we try to call `render`.
489491

490-
// CSANAD: This was not in sync with the previous chapter's instructions. At
491-
// chapter_03_unit_test_first_view.asciidoc#urlspy we said "We don’t need two
492-
// separate tests,(...) and got rid of the first version.
493-
494492
So why can't Django find the template?
495493
It's right where it's supposed to be, in the _lists/templates_ folder.
496494

@@ -638,8 +636,16 @@ class HomePageTest(TestCase):
638636
// (I derived this approach for test quality evaluation from this book: https://www.manning.com/books/unit-testing?query=%20Vladimir%20Khorikov)
639637

640638
The main point, though, is that instead of testing constants
641-
we're testing our implementation.
642-
Great!
639+
we're testing at a higher level of abstraction.
640+
Great!footnote:[
641+
I'm glossing over some trade-offs here.
642+
Yes, on the plus side, our tests no longer care about the specific content of our HTML
643+
so they are no longer brittle with respect to minor changes to the copy in our template.
644+
But on the other hand, they depend on some Django implementation details,
645+
so they are brittle with respect to changing the template rendering library,
646+
or even just renaming templates.
647+
So it's a compromise, as always, but `assertTemplateUsed()` is quite a common
648+
pattern in the Django world for a basic test of 200/GET requests]
643649

644650

645651

chapter_16_advanced_forms.asciidoc

+2-2
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ class ListAndItemModelsTest(TestCase):
314314
====
315315

316316
That's more than enough really--a check of the default values of attributes
317-
on a freshly initialized model object is enough to sanity-check that we've
317+
on a freshly initialized model object is enough to sense-check that we've
318318
probably set some fields up in 'models.py'. The "item is related to list" test
319319
is a real "belt and braces" test to make sure that our foreign key relationship
320320
works.
@@ -1248,7 +1248,7 @@ class ListViewTest(TestCase):
12481248
<6> For POST requests, make sure you test both the valid case and the invalid
12491249
case.
12501250
1251-
<7> Optionally, sanity-check that your form is rendered, and its errors are
1251+
<7> Optionally, sense-check that your form is rendered, and its errors are
12521252
displayed.
12531253
******************************************************************************
12541254

0 commit comments

Comments
 (0)