Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Test-Driven Django Development

Kevin Harvey
February 08, 2015

Test-Driven Django Development

Writing tests is not just about knowing whether your code is broken. TDD is a development methodology that simplifies software projects, helping you think through your app before you even write the first model. We'll cover what to test and how to test it, and walk through some of Django's testing tools in tons of examples and a TDD demo.

This presentation was given at PyTennessee on February 8, 2015.

Kevin Harvey

February 08, 2015
Tweet

More Decks by Kevin Harvey

Other Decks in Programming

Transcript

  1. What is a test? An evolutionary perspective def multiply_these_two_numbers(a,b): """

    Add 'b' to itself 'a' times """ i = 0 product = 0 while a > i: product += b i += 1 return product
  2. What is a test? An evolutionary perspective >>> from myapp.my_funcs

    import multiply_these_two_numbers >>> multiply_these_two_numbers(4,5) 20 >>> multiply_these_two_numbers(7,8) 56 >>> multiply_these_two_numbers(720,617) 444240
  3. What is a test? An evolutionary perspective >>> from myapp.my_funcs

    import multiply_these_two_numbers >>> multiply_these_two_numbers(4,5) 20
  4. What is a test? An evolutionary perspective from myapp.my_funcs import

    multiply_these_two_numbers import math ! def area_of_rectangle(length, width): """ Length times width """ return multiply_these_two_numbers(length, width) def surface_area_of_sphere(radius): """ 4 times pi times the radius squared """ radius_squared = multiply_these_two_numbers(radius, radius) four_times_pi = multiply_these_two_numbers(4, math.pi) return multiply_these_two_numbers(radius_squared, four_times_pi)
  5. What is a test? An evolutionary perspective from myapp.my_funcs import

    multiply_these_two_numbers ! print multiply_these_two_numbers(4,5) $ python my_tests.py 20 $
  6. What is a test? An evolutionary perspective from django.test import

    TestCase from myapp.my_funcs import multiply_these_two_numbers ! class SillyTest(TestCase): def test_multiply_these_two_numbers(self): """ Tests that the function knows how to do math """ self.assertEqual(multiply_these_two_numbers(4,5), 20)
  7. What is a test? An evolutionary perspective $ python manage.py

    test myapp Creating test database for alias 'default'... . --------------------------------------------------------- Ran 1 test in 0.000s ! OK Destroying test database for alias 'default'...
  8. Why write tests? UGLY CODE def multiply_these_two_numbers(a,b): """ Add 'b'

    to itself 'a' times """ i = 0 product = 0 while a > i: product += b i += 1 return product
  9. Why write tests? UGLY, VITAL CODE from myapp.my_funcs import multiply_these_two_numbers

    import math ! def area_of_rectangle(length, width): """ Length times width """ return multiply_these_two_numbers(length, width) def surface_area_of_sphere(radius): """ 4 times pi times the radius squared """ radius_squared = multiply_these_two_numbers(radius, radius) four_times_pi = multiply_these_two_numbers(4, math.pi) return multiply_these_two_numbers(radius_squared, four_times_pi)
  10. Why write tests? UGLY, VITAL, TESTED CODE from django.test import

    TestCase from myapp.my_funcs import multiply_these_two_numbers ! class SillyTest(TestCase): def test_multiply_these_two_numbers(self): """ Tests that the function knows how to do math """ self.assertEqual(multiply_these_two_numbers(4,5), 20)
  11. Why write tests? BETTER CODE def multiply_these_two_numbers(a,b): """ Multiply a

    times b """ # i = 0 # product = 0 # while a > i: # product += b # i += 1 # return product return a*b
  12. Why write tests? The test guarantees the refactor $ python

    manage.py test myapp Creating test database for alias 'default'... . --------------------------------------------------------- Ran 1 test in 0.000s ! OK Destroying test database for alias 'default'...
  13. Why write tests? Keep your dev database clean $ python

    manage.py test myapp Creating test database for alias 'default'... . --------------------------------------------------------- Ran 1 test in 0.000s ! OK Destroying test database for alias 'default'...
  14. Unit test for Model def test_questions_increment_votes_up(self): """ Test voting up

    a question """ question_1 = Question(text="How can my team get started?", votes=7) question_1.increment_votes(4) self.assertEquals(question_1.votes, 11) A few examples
  15. Unit test for Model class Question(models.Model): text = models.CharField("Question", max_length=500)

    votes = models.IntegerField() def increment_votes(self, num): self.votes += num self.save() A few examples
  16. Unit test for a Form def test_question_form_excludes_all_but_text(self): """ The user

    can only supply the text of a question """ form = QuestionForm() self.assertEquals(form.fields.keys(), ['text']) self.assertNotEquals(form.fields.keys(), ['text', 'votes']) A few examples
  17. Unit test for a POST action def test_ask_question(self): """ Test

    that POSTing the right data will result in a new question """ response = self.client.post('/ask/', {'text': 'Is there any more pizza?'}) self.assertRedirects(response, '/') self.assertEqual(Question.objects.filter(text='Is there any more pizza?').count(), 1) A few examples
  18. Unit test for a POST action def ask(request): if request.method

    == "POST": question_form = QuestionForm(request.POST) question_form.save() return HttpResponseRedirect('/') A few examples urlpatterns = patterns('', ... ! url(r'^ask/$', 'questions.views.ask', name='ask'), ...
  19. Functional test: Logging in def test_admin_can_manage_questions(self): ! self.browser.get(self.live_server_url + '/admin/')

    username = self.browser.find_element_by_css_selector("input#id_username") username.send_keys("peter") password = self.browser.find_element_by_css_selector("input#id_password") password.send_keys("password") self.browser.find_element_by_css_selector("input[type='submit']").click() ! body = self.browser.find_element_by_tag_name('body') self.assertIn('Site administration', body.text) A few examples
  20. TDD by Example Torquemada allows attendees at “Test-Driven Django Development”

    to inquire of the presenter asynchronously. Our example project: Torquemada http://bit.ly/1jmHfjp
  21. TDD by Example Use Case: Isabella the Inquisitive Isabella has

    learned a lot by attending the “Test-driven Django Development” at PyTennessee but still has a few questions for the presenter. She visits Torquemada in her web browser, where she can see if any of her inquiries have been addressed and see the question that is being currently discussed. She is able to ‘vote up’ questions she would like the presenter to answer, and ‘vote down’ questions she thinks are unimportant. She is also able to ask her own question. Our example project: Torquemada http://bit.ly/1jmHfjp
  22. TDD by Example Use Case: Peter the Presenter Peter is

    presenting“Test-driven Django Development” at PyTennessee and would like to answer any questions the attendees may have during set periods in the talk. He views Torquemada in his web browser to see what questions attendees have asked, and their relative importance based on the number of votes they’ve received. When the group is discussing a question, he uses the Django admin site to set the question’s status to “Current”. After the question has been discussed, he sets the status to “Archived” Our example project: Torquemada http://bit.ly/1jmHfjp
  23. TDD by example Text from django.test import LiveServerTestCase from selenium

    import webdriver ! class QuestionsTest(LiveServerTestCase): ! def setUp(self): self.browser = webdriver.Firefox() self.browser.implicitly_wait(3) ! def tearDown(self): self.browser.quit() ...
  24. TDD by example Text ... class QuestionsTest(LiveServerTestCase): ... def test_can_read_v..._a_question(self):

    # Isabel opens her web browser and # visits Torquemada self.browser.get(self.live_server_url + '/') ! # TODO self.fail('finish this test')
  25. TDD by example Text # She knows it's Torquemada because

    she sees the name in the heading heading = self.browser.find_element_by_css_selector("h1#trq-heading") self.assertEqual(heading.text, "Torquemada")
  26. TDD by example # run all the tests in our

    entire project python manage.py test ! # run all the tests for an app python manage.py test questions ! # run all the tests in a test case python manage.py test questions.HomePageViewTest ! # run a single test python manage.py test questions.HomePageViewTest.test_root_url_shows_questions
  27. Getting Around with Selenium text_field = self.browser.find_element_by_css_selector("#id_text") text_field.clear() text_field.send_keys("Why aren't

    you using reverse()?") ! self.browser.find_element_by_css_selector("input#trq-submit- question").click() Working with form elements
  28. How do I get started? I want to start testing

    on my team. What’s the best way?
  29. How do I get started? 3. Write unit tests for

    existing code you use while doing 1. and 2.
  30. IS

  31. IT

  32. Configuring Jenkins $ pip install django-jenkins INSTALLED_APPS = ( ...

    'django_jenkins', ) ... ! JENKINS_TASKS = ( 'django_jenkins.tasks.run_pylint', 'django_jenkins.tasks.with_coverage', 'django_jenkins.tasks.django_tests', # there are more of these ) ! $ python manage.py jenkins # Jenkins will run this command
  33. Configuring Jenkins 1. Configure a new test (name, description) 2.

    Give it your repo URL 3. Tell it how often to build 4. Tell it the commands to run 5. Configure where to save the reports 6. Click “Build Now”
  34. Using fixtures [ { "pk": 1, "model": "questions.question", "fields": {

    "status": "new", "text": "How can my team get started with testing?", "votes": 0, "created": "2013-01-17T16:15:37.786Z" } }, { "pk": 2, "model": "questions.question", "fields": { "status": "new", "text": "Does Selenium only work in Firefox?", "votes": 0, "created": "2013-01-17T16:17:48.381Z" } } ]
  35. Using fixtures $ python manage.py runserver ... ! # Use

    the Django /admin site to make some test data ! ... ! $ mkdir questions/fixtures/ $ python manage.py dumpdata questions --indent=4 > questions/fixtures/questions.json Do NOT try to write fixtures by hand