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

Test-Driven Development with Python

Kevin Harvey
February 07, 2016

Test-Driven Development with Python

A TDD tutorial, featuring unittest, LiveServerTestCase, and Selenium. Originally presented at PyTennessee 2016.

Kevin Harvey

February 07, 2016
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 # my_tests.py 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 unittest 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 unittest 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): """ Add 'b'

    to itself 'a' times """ # 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. Types of Tests 1. Functional tests 2. Unit tests 3.

    Performance tests (which I won’t cover)
  14. Types of Tests 1. Functional tests 2. Unit tests 3.

    Performance tests (which I won’t cover)
  15. Unit test for Django 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
  16. Unit test for Django 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
  17. Arrange, Act, Assert 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) The Three A’s
  18. Arrange, Act, Assert 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) The Three A’s
  19. Arrange, Act, Assert 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) The Three A’s
  20. Arrange, Act, Assert 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) The Three A’s
  21. 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
  22. 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
  23. 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'), ...
  24. 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
  25. Invoking a command line utility # test.py import unittest import

    subprocess class StardateTestCase(unittest.TestCase): def test_stardate(self): """ Test that we return the current stardate """ output = subprocess.check_output('stardate', shell=True) self.assertEqual(output, '93705.51') if __name__ == '__main__': unittest.main() TDD a CLI
  26. Invoking a command line utility $ python test.py /bin/sh: stardate:

    command not found E ================================================================= ERROR: test_stardate (__main__.StardateTestCase) Test that we return the current stardate ----------------------------------------------------------------- Traceback (most recent call last): File "test.py", line 11, in test_stardate output = subprocess.check_output("stardate", shell=True) File "/usr/local/Cellar/python/2.7.10_2/Frameworks/ Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 573, in check_output raise CalledProcessError(retcode, cmd, output=output) CalledProcessError: Command 'stardate' returned non-zero exit status 127 ----------------------------------------------------------------- TDD a CLI
  27. TDD by Example Torquemada allows attendees at “Test-Driven Development with

    Python” to inquire of the presenter asynchronously. Our example project: Torquemada http://bit.ly/1jmHfjp
  28. 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() ...
  29. 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')
  30. 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")
  31. 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
  32. 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
  33. How do I get started? I want to start testing

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

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

  36. IT

  37. 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
  38. 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”