@@ -45,25 +45,22 @@ so do stick it out!
45
45
46
46
((("Test-Driven Development (TDD)", "additional resources")))
47
47
((("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!
51
50
*******************************************************************************
52
51
53
52
54
53
55
-
56
54
=== Our First Django App, and Our First Unit Test
57
55
58
56
((("Django framework", "code structure in")))
59
57
((("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.
67
64
68
65
Let's start an app for our to-do lists:
69
66
@@ -144,8 +141,8 @@ You can see that, all the way through,
144
141
the functional tests are driving what development we do from a high level,
145
142
while the unit tests drive what we do at a low level.
146
143
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.
149
146
The unit tests are there to exhaustively check all the lower level details and corner cases.
150
147
151
148
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.
172
169
|Provides confidence that everything is wired together correctly, works end-to-end
173
170
|Can exhaustively check permutations, details, edge cases
174
171
175
- |===
172
+ |Can warn about problems without telling you exactly what's wrong
173
+ |Can point at exactly where the problem is
176
174
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
+ |===
179
178
180
179
=== Unit Testing in Django
181
180
@@ -195,18 +194,19 @@ from django.test import TestCase
195
194
====
196
195
197
196
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.
202
201
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:
210
210
211
211
[role="sourcecode"]
212
212
.lists/tests.py (ch03l002)
@@ -223,8 +223,8 @@ class SmokeTest(TestCase):
223
223
====
224
224
225
225
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#:
228
228
229
229
230
230
[subs="specialcharacters,macros"]
@@ -250,8 +250,8 @@ FAILED (failures=1)
250
250
Destroying test database for alias 'default'...
251
251
----
252
252
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:
255
255
256
256
257
257
[subs="specialcharacters,quotes"]
@@ -293,13 +293,6 @@ Django's workflow goes something like this:
293
293
the request (this is referred to as _resolving_ the URL).
294
294
3. The view function processes the request and returns an HTTP _response_.
295
295
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
-
303
296
304
297
So, we want to test two things:
305
298
@@ -315,12 +308,6 @@ Let's start with the first.
315
308
316
309
=== Unit Testing a View
317
310
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
-
324
311
((("unit tests", "in Django", "unit testing a view", secondary-sortas="Django")))
325
312
Open up _lists/tests.py_, and change our silly test to something like this:
326
313
@@ -368,10 +355,9 @@ So, to test that:
368
355
with the words "To-Do lists" in it--because
369
356
that's what we specified in our functional test.
370
357
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
372
359
that it starts with an `<html>` tag which gets closed at the end.
373
360
374
- // DAVID: 'sanity check' is seen by some people as ableist, might be worth removing the 'sanity'?
375
361
376
362
So, what do you think will happen when we run the tests?
377
363
@@ -544,12 +530,6 @@ from django.http import HttpResponse
544
530
def home_page(request):
545
531
return HttpResponse()
546
532
----
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
553
533
====
554
534
555
535
* Tests again:
@@ -681,7 +661,7 @@ FAILED (failures=1)
681
661
Looks like something isn't quite right. This is the reason we have functional
682
662
tests!
683
663
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:
685
665
firstly create a view function to produce responses for requests,
686
666
and secondly tell the server which functions should respond to which URLs?
687
667
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.
694
674
Django, like most web frameworks, supplies a tool for doing just that, called the
695
675
https://docs.djangoproject.com/en/5.2/topics/testing/tools/#the-test-client[Django Test Client].
696
676
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
-
701
677
Let's see how to use it by adding a second, alternative test to our unit tests:
702
678
703
679
[role="sourcecode"]
@@ -706,7 +682,7 @@ Let's see how to use it by adding a second, alternative test to our unit tests:
706
682
[source,python]
707
683
----
708
684
class HomePageTest(TestCase):
709
- def test_home_page_returns_correct_html(self):
685
+ def test_home_page_returns_correct_html(self): <1>
710
686
request = HttpRequest()
711
687
response = home_page(request)
712
688
html = response.content.decode("utf8")
@@ -715,16 +691,14 @@ class HomePageTest(TestCase):
715
691
self.assertTrue(html.endswith("</html>"))
716
692
717
693
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 >
720
696
----
721
697
====
722
698
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.
726
700
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`,
728
702
which is available on any test that uses `django.test.TestCase`.
729
703
It provides methods like `.get()` which simulate a browser making http requests,
730
704
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
815
789
case it's all Django code, but we'll see plenty of examples of this fifth step
816
790
later in the book.
817
791
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.
828
796
829
797
In other words, Django isn't yet configured to respond to requests for the
830
798
root URL ("/") of our site. Let's make that happen now.
@@ -869,13 +837,6 @@ urlpatterns = [
869
837
----
870
838
====
871
839
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
-
879
840
As usual, lots of helpful comments and default suggestions from Django.
880
841
In fact, that very first example is pretty much exactly what we want!
881
842
Let's use that, with some minor changes.
@@ -902,8 +863,8 @@ urlpatterns = [
902
863
<3> And we wire it up here, as a `path()` entry in the `urlpatterns` global.
903
864
Django strips the leading slash from all urls,
904
865
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"
907
868
908
869
Now we can run our unit tests again, with *`python manage.py test`*:
909
870
0 commit comments