tutorial05.txt 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697
  1. =====================================
  2. Writing your first Django app, part 5
  3. =====================================
  4. This tutorial begins where :doc:`Tutorial 4 </intro/tutorial04>` left off.
  5. We've built a Web-poll application, and we'll now create some automated tests
  6. for it.
  7. Introducing automated testing
  8. =============================
  9. What are automated tests?
  10. -------------------------
  11. Tests are simple routines that check the operation of your code.
  12. Testing operates at different levels. Some tests might apply to a tiny detail
  13. (*does a particular model method return values as expected?*) while others
  14. examine the overall operation of the software (*does a sequence of user inputs
  15. on the site produce the desired result?*). That's no different from the kind of
  16. testing you did earlier in :doc:`Tutorial 1 </intro/tutorial01>`, using the
  17. :djadmin:`shell` to examine the behavior of a method, or running the
  18. application and entering data to check how it behaves.
  19. What's different in *automated* tests is that the testing work is done for
  20. you by the system. You create a set of tests once, and then as you make changes
  21. to your app, you can check that your code still works as you originally
  22. intended, without having to perform time consuming manual testing.
  23. Why you need to create tests
  24. ----------------------------
  25. So why create tests, and why now?
  26. You may feel that you have quite enough on your plate just learning
  27. Python/Django, and having yet another thing to learn and do may seem
  28. overwhelming and perhaps unnecessary. After all, our polls application is
  29. working quite happily now; going through the trouble of creating automated
  30. tests is not going to make it work any better. If creating the polls
  31. application is the last bit of Django programming you will ever do, then true,
  32. you don't need to know how to create automated tests. But, if that's not the
  33. case, now is an excellent time to learn.
  34. Tests will save you time
  35. ~~~~~~~~~~~~~~~~~~~~~~~~
  36. Up to a certain point, 'checking that it seems to work' will be a satisfactory
  37. test. In a more sophisticated application, you might have dozens of complex
  38. interactions between components.
  39. A change in any of those components could have unexpected consequences on the
  40. application's behavior. Checking that it still 'seems to work' could mean
  41. running through your code's functionality with twenty different variations of
  42. your test data just to make sure you haven't broken something - not a good use
  43. of your time.
  44. That's especially true when automated tests could do this for you in seconds.
  45. If something's gone wrong, tests will also assist in identifying the code
  46. that's causing the unexpected behavior.
  47. Sometimes it may seem a chore to tear yourself away from your productive,
  48. creative programming work to face the unglamorous and unexciting business
  49. of writing tests, particularly when you know your code is working properly.
  50. However, the task of writing tests is a lot more fulfilling than spending hours
  51. testing your application manually or trying to identify the cause of a
  52. newly-introduced problem.
  53. Tests don't just identify problems, they prevent them
  54. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  55. It's a mistake to think of tests merely as a negative aspect of development.
  56. Without tests, the purpose or intended behavior of an application might be
  57. rather opaque. Even when it's your own code, you will sometimes find yourself
  58. poking around in it trying to find out what exactly it's doing.
  59. Tests change that; they light up your code from the inside, and when something
  60. goes wrong, they focus light on the part that has gone wrong - *even if you
  61. hadn't even realized it had gone wrong*.
  62. Tests make your code more attractive
  63. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  64. You might have created a brilliant piece of software, but you will find that
  65. many other developers will simply refuse to look at it because it lacks tests;
  66. without tests, they won't trust it. Jacob Kaplan-Moss, one of Django's
  67. original developers, says "Code without tests is broken by design."
  68. That other developers want to see tests in your software before they take it
  69. seriously is yet another reason for you to start writing tests.
  70. Tests help teams work together
  71. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  72. The previous points are written from the point of view of a single developer
  73. maintaining an application. Complex applications will be maintained by teams.
  74. Tests guarantee that colleagues don't inadvertently break your code (and that
  75. you don't break theirs without knowing). If you want to make a living as a
  76. Django programmer, you must be good at writing tests!
  77. Basic testing strategies
  78. ========================
  79. There are many ways to approach writing tests.
  80. Some programmers follow a discipline called "`test-driven development`_"; they
  81. actually write their tests before they write their code. This might seem
  82. counter-intuitive, but in fact it's similar to what most people will often do
  83. anyway: they describe a problem, then create some code to solve it. Test-driven
  84. development simply formalizes the problem in a Python test case.
  85. More often, a newcomer to testing will create some code and later decide that
  86. it should have some tests. Perhaps it would have been better to write some
  87. tests earlier, but it's never too late to get started.
  88. Sometimes it's difficult to figure out where to get started with writing tests.
  89. If you have written several thousand lines of Python, choosing something to
  90. test might not be easy. In such a case, it's fruitful to write your first test
  91. the next time you make a change, either when you add a new feature or fix a bug.
  92. So let's do that right away.
  93. .. _test-driven development: http://en.wikipedia.org/wiki/Test-driven_development
  94. Writing our first test
  95. ======================
  96. We identify a bug
  97. -----------------
  98. Fortunately, there's a little bug in the ``polls`` application for us to fix
  99. right away: the ``Question.was_published_recently()`` method returns ``True`` if
  100. the ``Question`` was published within the last day (which is correct) but also if
  101. the ``Question``’s ``pub_date`` field is in the future (which certainly isn't).
  102. You can see this in the Admin; create a question whose date lies in the future;
  103. you'll see that the ``Question`` change list claims it was published recently.
  104. You can also see this using the :djadmin:`shell`::
  105. >>> import datetime
  106. >>> from django.utils import timezone
  107. >>> from polls.models import Question
  108. >>> # create a Question instance with pub_date 30 days in the future
  109. >>> future_question = Question(pub_date=timezone.now() + datetime.timedelta(days=30))
  110. >>> # was it published recently?
  111. >>> future_question.was_published_recently()
  112. True
  113. Since things in the future are not 'recent', this is clearly wrong.
  114. Create a test to expose the bug
  115. -------------------------------
  116. What we've just done in the :djadmin:`shell` to test for the problem is exactly
  117. what we can do in an automated test, so let's turn that into an automated test.
  118. A conventional place for an application's tests is in the application's
  119. ``tests.py`` file; the testing system will automatically find tests in any file
  120. whose name begins with ``test``.
  121. Put the following in the ``tests.py`` file in the ``polls`` application:
  122. .. snippet::
  123. :filename: polls/tests.py
  124. import datetime
  125. from django.utils import timezone
  126. from django.test import TestCase
  127. from .models import Question
  128. class QuestionMethodTests(TestCase):
  129. def test_was_published_recently_with_future_question(self):
  130. """
  131. was_published_recently() should return False for questions whose
  132. pub_date is in the future
  133. """
  134. time = timezone.now() + datetime.timedelta(days=30)
  135. future_question = Question(pub_date=time)
  136. self.assertEqual(future_question.was_published_recently(), False)
  137. What we have done here is created a :class:`django.test.TestCase` subclass
  138. with a method that creates a ``Question`` instance with a ``pub_date`` in the
  139. future. We then check the output of ``was_published_recently()`` - which
  140. *ought* to be False.
  141. Running tests
  142. -------------
  143. In the terminal, we can run our test::
  144. $ python manage.py test polls
  145. and you'll see something like::
  146. Creating test database for alias 'default'...
  147. F
  148. ======================================================================
  149. FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionMethodTests)
  150. ----------------------------------------------------------------------
  151. Traceback (most recent call last):
  152. File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question
  153. self.assertEqual(future_question.was_published_recently(), False)
  154. AssertionError: True != False
  155. ----------------------------------------------------------------------
  156. Ran 1 test in 0.001s
  157. FAILED (failures=1)
  158. Destroying test database for alias 'default'...
  159. What happened is this:
  160. * ``python manage.py test polls`` looked for tests in the ``polls`` application
  161. * it found a subclass of the :class:`django.test.TestCase` class
  162. * it created a special database for the purpose of testing
  163. * it looked for test methods - ones whose names begin with ``test``
  164. * in ``test_was_published_recently_with_future_question`` it created a ``Question``
  165. instance whose ``pub_date`` field is 30 days in the future
  166. * ... and using the ``assertEqual()`` method, it discovered that its
  167. ``was_published_recently()`` returns ``True``, though we wanted it to return
  168. ``False``
  169. The test informs us which test failed and even the line on which the failure
  170. occurred.
  171. Fixing the bug
  172. --------------
  173. We already know what the problem is: ``Question.was_published_recently()`` should
  174. return ``False`` if its ``pub_date`` is in the future. Amend the method in
  175. ``models.py``, so that it will only return ``True`` if the date is also in the
  176. past:
  177. .. snippet::
  178. :filename: polls/models.py
  179. def was_published_recently(self):
  180. now = timezone.now()
  181. return now - datetime.timedelta(days=1) <= self.pub_date <= now
  182. and run the test again::
  183. Creating test database for alias 'default'...
  184. .
  185. ----------------------------------------------------------------------
  186. Ran 1 test in 0.001s
  187. OK
  188. Destroying test database for alias 'default'...
  189. After identifying a bug, we wrote a test that exposes it and corrected the bug
  190. in the code so our test passes.
  191. Many other things might go wrong with our application in the future, but we can
  192. be sure that we won't inadvertently reintroduce this bug, because simply
  193. running the test will warn us immediately. We can consider this little portion
  194. of the application pinned down safely forever.
  195. More comprehensive tests
  196. ------------------------
  197. While we're here, we can further pin down the ``was_published_recently()``
  198. method; in fact, it would be positively embarrassing if in fixing one bug we had
  199. introduced another.
  200. Add two more test methods to the same class, to test the behavior of the method
  201. more comprehensively:
  202. .. snippet::
  203. :filename: polls/tests.py
  204. def test_was_published_recently_with_old_question(self):
  205. """
  206. was_published_recently() should return False for questions whose
  207. pub_date is older than 1 day
  208. """
  209. time = timezone.now() - datetime.timedelta(days=30)
  210. old_question = Question(pub_date=time)
  211. self.assertEqual(old_question.was_published_recently(), False)
  212. def test_was_published_recently_with_recent_question(self):
  213. """
  214. was_published_recently() should return True for questions whose
  215. pub_date is within the last day
  216. """
  217. time = timezone.now() - datetime.timedelta(hours=1)
  218. recent_question = Question(pub_date=time)
  219. self.assertEqual(recent_question.was_published_recently(), True)
  220. And now we have three tests that confirm that ``Question.was_published_recently()``
  221. returns sensible values for past, recent, and future questions.
  222. Again, ``polls`` is a simple application, but however complex it grows in the
  223. future and whatever other code it interacts with, we now have some guarantee
  224. that the method we have written tests for will behave in expected ways.
  225. Test a view
  226. ===========
  227. The polls application is fairly undiscriminating: it will publish any question,
  228. including ones whose ``pub_date`` field lies in the future. We should improve
  229. this. Setting a ``pub_date`` in the future should mean that the Question is
  230. published at that moment, but invisible until then.
  231. A test for a view
  232. -----------------
  233. When we fixed the bug above, we wrote the test first and then the code to fix
  234. it. In fact that was a simple example of test-driven development, but it
  235. doesn't really matter in which order we do the work.
  236. In our first test, we focused closely on the internal behavior of the code. For
  237. this test, we want to check its behavior as it would be experienced by a user
  238. through a web browser.
  239. Before we try to fix anything, let's have a look at the tools at our disposal.
  240. The Django test client
  241. ----------------------
  242. Django provides a test :class:`~django.test.Client` to simulate a user
  243. interacting with the code at the view level. We can use it in ``tests.py``
  244. or even in the :djadmin:`shell`.
  245. We will start again with the :djadmin:`shell`, where we need to do a couple of
  246. things that won't be necessary in ``tests.py``. The first is to set up the test
  247. environment in the :djadmin:`shell`::
  248. >>> from django.test.utils import setup_test_environment
  249. >>> setup_test_environment()
  250. :meth:`~django.test.utils.setup_test_environment` installs a template renderer
  251. which will allow us to examine some additional attributes on responses such as
  252. ``response.context`` that otherwise wouldn't be available. Note that this
  253. method *does not* setup a test database, so the following will be run against
  254. the existing database and the output may differ slightly depending on what
  255. questions you already created.
  256. Next we need to import the test client class (later in ``tests.py`` we will use
  257. the :class:`django.test.TestCase` class, which comes with its own client, so
  258. this won't be required)::
  259. >>> from django.test import Client
  260. >>> # create an instance of the client for our use
  261. >>> client = Client()
  262. With that ready, we can ask the client to do some work for us::
  263. >>> # get a response from '/'
  264. >>> response = client.get('/')
  265. >>> # we should expect a 404 from that address
  266. >>> response.status_code
  267. 404
  268. >>> # on the other hand we should expect to find something at '/polls/'
  269. >>> # we'll use 'reverse()' rather than a hardcoded URL
  270. >>> from django.core.urlresolvers import reverse
  271. >>> response = client.get(reverse('polls:index'))
  272. >>> response.status_code
  273. 200
  274. >>> response.content
  275. '\n\n\n <p>No polls are available.</p>\n\n'
  276. >>> # note - you might get unexpected results if your ``TIME_ZONE``
  277. >>> # in ``settings.py`` is not correct. If you need to change it,
  278. >>> # you will also need to restart your shell session
  279. >>> from polls.models import Question
  280. >>> from django.utils import timezone
  281. >>> # create a Question and save it
  282. >>> q = Question(question_text="Who is your favorite Beatle?", pub_date=timezone.now())
  283. >>> q.save()
  284. >>> # check the response once again
  285. >>> response = client.get('/polls/')
  286. >>> response.content
  287. '\n\n\n <ul>\n \n <li><a href="/polls/1/">Who is your favorite Beatle?</a></li>\n \n </ul>\n\n'
  288. >>> # If the following doesn't work, you probably omitted the call to
  289. >>> # setup_test_environment() described above
  290. >>> response.context['latest_question_list']
  291. [<Question: Who is your favorite Beatle?>]
  292. Improving our view
  293. ------------------
  294. The list of polls shows polls that aren't published yet (i.e. those that have a
  295. ``pub_date`` in the future). Let's fix that.
  296. In :ref:`Tutorial 4 <tutorial04-amend-views>` we introduced a class-based view,
  297. based on :class:`~django.views.generic.list.ListView`:
  298. .. snippet::
  299. :filename: polls/views.py
  300. class IndexView(generic.ListView):
  301. template_name = 'polls/index.html'
  302. context_object_name = 'latest_question_list'
  303. def get_queryset(self):
  304. """Return the last five published questions."""
  305. return Question.objects.order_by('-pub_date')[:5]
  306. ``response.context_data['latest_question_list']`` extracts the data this view
  307. places into the context.
  308. We need to amend the ``get_queryset`` method and change it so that it also
  309. checks the date by comparing it with ``timezone.now()``. First we need to add
  310. an import:
  311. .. snippet::
  312. :filename: polls/views.py
  313. from django.utils import timezone
  314. and then we must amend the ``get_queryset`` method like so:
  315. .. snippet::
  316. :filename: polls/views.py
  317. def get_queryset(self):
  318. """
  319. Return the last five published questions (not including those set to be
  320. published in the future).
  321. """
  322. return Question.objects.filter(
  323. pub_date__lte=timezone.now()
  324. ).order_by('-pub_date')[:5]
  325. ``Question.objects.filter(pub_date__lte=timezone.now())`` returns a queryset
  326. containing ``Question``\s whose ``pub_date`` is less than or equal to - that
  327. is, earlier than or equal to - ``timezone.now``.
  328. Testing our new view
  329. --------------------
  330. Now you can satisfy yourself that this behaves as expected by firing up the
  331. runserver, loading the site in your browser, creating ``Questions`` with dates
  332. in the past and future, and checking that only those that have been published
  333. are listed. You don't want to have to do that *every single time you make any
  334. change that might affect this* - so let's also create a test, based on our
  335. :djadmin:`shell` session above.
  336. Add the following to ``polls/tests.py``:
  337. .. snippet::
  338. :filename: polls/tests.py
  339. from django.core.urlresolvers import reverse
  340. and we'll create a shortcut function to create questions as well as a new test
  341. class:
  342. .. snippet::
  343. :filename: polls/tests.py
  344. def create_question(question_text, days):
  345. """
  346. Creates a question with the given `question_text` published the given
  347. number of `days` offset to now (negative for questions published
  348. in the past, positive for questions that have yet to be published).
  349. """
  350. time = timezone.now() + datetime.timedelta(days=days)
  351. return Question.objects.create(question_text=question_text,
  352. pub_date=time)
  353. class QuestionViewTests(TestCase):
  354. def test_index_view_with_no_questions(self):
  355. """
  356. If no questions exist, an appropriate message should be displayed.
  357. """
  358. response = self.client.get(reverse('polls:index'))
  359. self.assertEqual(response.status_code, 200)
  360. self.assertContains(response, "No polls are available.")
  361. self.assertQuerysetEqual(response.context['latest_question_list'], [])
  362. def test_index_view_with_a_past_question(self):
  363. """
  364. Questions with a pub_date in the past should be displayed on the
  365. index page
  366. """
  367. create_question(question_text="Past question.", days=-30)
  368. response = self.client.get(reverse('polls:index'))
  369. self.assertQuerysetEqual(
  370. response.context['latest_question_list'],
  371. ['<Question: Past question.>']
  372. )
  373. def test_index_view_with_a_future_question(self):
  374. """
  375. Questions with a pub_date in the future should not be displayed on
  376. the index page.
  377. """
  378. create_question(question_text="Future question.", days=30)
  379. response = self.client.get(reverse('polls:index'))
  380. self.assertContains(response, "No polls are available.",
  381. status_code=200)
  382. self.assertQuerysetEqual(response.context['latest_question_list'], [])
  383. def test_index_view_with_future_question_and_past_question(self):
  384. """
  385. Even if both past and future questions exist, only past questions
  386. should be displayed.
  387. """
  388. create_question(question_text="Past question.", days=-30)
  389. create_question(question_text="Future question.", days=30)
  390. response = self.client.get(reverse('polls:index'))
  391. self.assertQuerysetEqual(
  392. response.context['latest_question_list'],
  393. ['<Question: Past question.>']
  394. )
  395. def test_index_view_with_two_past_questions(self):
  396. """
  397. The questions index page may display multiple questions.
  398. """
  399. create_question(question_text="Past question 1.", days=-30)
  400. create_question(question_text="Past question 2.", days=-5)
  401. response = self.client.get(reverse('polls:index'))
  402. self.assertQuerysetEqual(
  403. response.context['latest_question_list'],
  404. ['<Question: Past question 2.>', '<Question: Past question 1.>']
  405. )
  406. Let's look at some of these more closely.
  407. First is a question shortcut function, ``create_question``, to take some
  408. repetition out of the process of creating questions.
  409. ``test_index_view_with_no_questions`` doesn't create any questions, but checks
  410. the message: "No polls are available." and verifies the ``latest_question_list``
  411. is empty. Note that the :class:`django.test.TestCase` class provides some
  412. additional assertion methods. In these examples, we use
  413. :meth:`~django.test.SimpleTestCase.assertContains()` and
  414. :meth:`~django.test.TransactionTestCase.assertQuerysetEqual()`.
  415. In ``test_index_view_with_a_past_question``, we create a question and verify that it
  416. appears in the list.
  417. In ``test_index_view_with_a_future_question``, we create a question with a
  418. ``pub_date`` in the future. The database is reset for each test method, so the
  419. first question is no longer there, and so again the index shouldn't have any
  420. questions in it.
  421. And so on. In effect, we are using the tests to tell a story of admin input
  422. and user experience on the site, and checking that at every state and for every
  423. new change in the state of the system, the expected results are published.
  424. Testing the ``DetailView``
  425. --------------------------
  426. What we have works well; however, even though future questions don't appear in
  427. the *index*, users can still reach them if they know or guess the right URL. So
  428. we need to add a similar constraint to ``DetailView``:
  429. .. snippet::
  430. :filename: polls/views.py
  431. class DetailView(generic.DetailView):
  432. ...
  433. def get_queryset(self):
  434. """
  435. Excludes any questions that aren't published yet.
  436. """
  437. return Question.objects.filter(pub_date__lte=timezone.now())
  438. And of course, we will add some tests, to check that a ``Question`` whose
  439. ``pub_date`` is in the past can be displayed, and that one with a ``pub_date``
  440. in the future is not:
  441. .. snippet::
  442. :filename: polls/tests.py
  443. class QuestionIndexDetailTests(TestCase):
  444. def test_detail_view_with_a_future_question(self):
  445. """
  446. The detail view of a question with a pub_date in the future should
  447. return a 404 not found.
  448. """
  449. future_question = create_question(question_text='Future question.',
  450. days=5)
  451. response = self.client.get(reverse('polls:detail',
  452. args=(future_question.id,)))
  453. self.assertEqual(response.status_code, 404)
  454. def test_detail_view_with_a_past_question(self):
  455. """
  456. The detail view of a question with a pub_date in the past should
  457. display the question's text.
  458. """
  459. past_question = create_question(question_text='Past Question.',
  460. days=-5)
  461. response = self.client.get(reverse('polls:detail',
  462. args=(past_question.id,)))
  463. self.assertContains(response, past_question.question_text,
  464. status_code=200)
  465. Ideas for more tests
  466. --------------------
  467. We ought to add a similar ``get_queryset`` method to ``ResultsView`` and
  468. create a new test class for that view. It'll be very similar to what we have
  469. just created; in fact there will be a lot of repetition.
  470. We could also improve our application in other ways, adding tests along the
  471. way. For example, it's silly that ``Questions`` can be published on the site
  472. that have no ``Choices``. So, our views could check for this, and exclude such
  473. ``Questions``. Our tests would create a ``Question`` without ``Choices`` and
  474. then test that it's not published, as well as create a similar ``Question``
  475. *with* ``Choices``, and test that it *is* published.
  476. Perhaps logged-in admin users should be allowed to see unpublished
  477. ``Questions``, but not ordinary visitors. Again: whatever needs to be added to
  478. the software to accomplish this should be accompanied by a test, whether you
  479. write the test first and then make the code pass the test, or work out the
  480. logic in your code first and then write a test to prove it.
  481. At a certain point you are bound to look at your tests and wonder whether your
  482. code is suffering from test bloat, which brings us to:
  483. When testing, more is better
  484. ============================
  485. It might seem that our tests are growing out of control. At this rate there will
  486. soon be more code in our tests than in our application, and the repetition
  487. is unaesthetic, compared to the elegant conciseness of the rest of our code.
  488. **It doesn't matter**. Let them grow. For the most part, you can write a test
  489. once and then forget about it. It will continue performing its useful function
  490. as you continue to develop your program.
  491. Sometimes tests will need to be updated. Suppose that we amend our views so that
  492. only ``Questions`` with ``Choices`` are published. In that case, many of our
  493. existing tests will fail - *telling us exactly which tests need to be amended to
  494. bring them up to date*, so to that extent tests help look after themselves.
  495. At worst, as you continue developing, you might find that you have some tests
  496. that are now redundant. Even that's not a problem; in testing redundancy is
  497. a *good* thing.
  498. As long as your tests are sensibly arranged, they won't become unmanageable.
  499. Good rules-of-thumb include having:
  500. * a separate ``TestClass`` for each model or view
  501. * a separate test method for each set of conditions you want to test
  502. * test method names that describe their function
  503. Further testing
  504. ===============
  505. This tutorial only introduces some of the basics of testing. There's a great
  506. deal more you can do, and a number of very useful tools at your disposal to
  507. achieve some very clever things.
  508. For example, while our tests here have covered some of the internal logic of a
  509. model and the way our views publish information, you can use an "in-browser"
  510. framework such as Selenium_ to test the way your HTML actually renders in a
  511. browser. These tools allow you to check not just the behavior of your Django
  512. code, but also, for example, of your JavaScript. It's quite something to see
  513. the tests launch a browser, and start interacting with your site, as if a human
  514. being were driving it! Django includes :class:`~django.test.LiveServerTestCase`
  515. to facilitate integration with tools like Selenium.
  516. If you have a complex application, you may want to run tests automatically
  517. with every commit for the purposes of `continuous integration`_, so that
  518. quality control is itself - at least partially - automated.
  519. A good way to spot untested parts of your application is to check code
  520. coverage. This also helps identify fragile or even dead code. If you can't test
  521. a piece of code, it usually means that code should be refactored or removed.
  522. Coverage will help to identify dead code. See
  523. :ref:`topics-testing-code-coverage` for details.
  524. :doc:`Testing in Django </topics/testing/index>` has comprehensive
  525. information about testing.
  526. .. _Selenium: http://seleniumhq.org/
  527. .. _continuous integration: http://en.wikipedia.org/wiki/Continuous_integration
  528. What's next?
  529. ============
  530. For full details on testing, see :doc:`Testing in Django
  531. </topics/testing/index>`.
  532. When you're comfortable with testing Django views, read
  533. :doc:`part 6 of this tutorial</intro/tutorial06>` to learn about
  534. static files management.