Slide 1

Slide 1 text

Test-Driven Django Development ! 8 February 2015 Kevin Harvey @kevinharvey [email protected]

Slide 2

Slide 2 text

Who is this guy? Senior Systems Engineer at SmileCareClub Djangonaut since 2007 (version 0.96)

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Who are y’all? A quick poll...

Slide 5

Slide 5 text

What is a test? An evolutionary perspective

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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)

Slide 10

Slide 10 text

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 $

Slide 11

Slide 11 text

What is a test? An evolutionary perspective >>> import time >>> time.sleep(604800)

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

What is a test? An evolutionary perspective $ python my_tests.py 20 $

Slide 14

Slide 14 text

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)

Slide 15

Slide 15 text

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'...

Slide 16

Slide 16 text

Why write tests? Why are we doing this?

Slide 17

Slide 17 text

Why write tests? 1. Drink More Beer

Slide 18

Slide 18 text

Why write tests? 2. Take the fear out of refactoring

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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)

Slide 21

Slide 21 text

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)

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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'...

Slide 24

Slide 24 text

Why write tests? 3. Explain your code

Slide 25

Slide 25 text

Why write tests? 4. Clarify your thinking

Slide 26

Slide 26 text

Why write tests? 5. Allow more developers to contribute

Slide 27

Slide 27 text

Why write tests? 6. Be taken seriously

Slide 28

Slide 28 text

Why write tests? 7. Try something out before you screw up your dev environment

Slide 29

Slide 29 text

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'...

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

What’s the Difference? Fewer things (ideally one) Many things (possibly all) Unit Functional

Slide 32

Slide 32 text

What’s the Difference? Small, write many Big, write few Unit Functional

Slide 33

Slide 33 text

What’s the Difference? Stuff developers care about Stuff users care about Unit Functional

Slide 34

Slide 34 text

What’s the Difference? Fast Slow Unit Functional

Slide 35

Slide 35 text

A few examples Django tests in action!

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Unit test for a Form class QuestionForm(forms.ModelForm): class Meta: model = Question fields = ('text',) A few examples

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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'), ...

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Functional test: Logging in # set up the admin site A few examples

Slide 44

Slide 44 text

Test Driven Development What is Test Driven Development?

Slide 45

Slide 45 text

TDD by Example Our example project: Torquemada

Slide 46

Slide 46 text

TDD by Example http://bit.ly/1jmHfjp or http://floating-inlet-6675.herokuapp.com/ ! code: https://github.com/kcharvey/Test-driven-Django- Development or use the link at the demo

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Let’s do this. TDD by example

Slide 51

Slide 51 text

Let’s do this Let’s do this.

Slide 52

Slide 52 text

No content

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

Text

Slide 55

Slide 55 text

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() ...

Slide 56

Slide 56 text

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')

Slide 57

Slide 57 text

Text

Slide 58

Slide 58 text

Text

Slide 59

Slide 59 text

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")

Slide 60

Slide 60 text

Text

Slide 61

Slide 61 text

We have a working (but failing) test. Commit it. TDD by example

Slide 62

Slide 62 text

So, where are we exactly? TDD by example

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

No content

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

No content

Slide 73

Slide 73 text

Rinse and repeat. TDD by example

Slide 74

Slide 74 text

Getting Around with Selenium self.browser.get()

Slide 75

Slide 75 text

Getting Around with Selenium self.browser.find_element_by_()

Slide 76

Slide 76 text

Getting Around with Selenium find_element_by_id find_element_by_name find_element_by_xpath find_element_by_link_text find_element_by_partial_link_text find_element_by_tag_name find_element_by_class_name find_element_by_css_selector # use this one

Slide 77

Slide 77 text

Getting Around with Selenium find_elements_by_id find_elements_by_name find_elements_by_xpath find_elements_by_link_text find_elements_by_partial_link_text find_elements_by_tag_name find_elements_by_class_name find_elements_by_css_selector ! # return lists

Slide 78

Slide 78 text

Getting Around with Selenium element.click()

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

How do I get started? I want to start testing on my team. What’s the best way?

Slide 81

Slide 81 text

How do I get started? 1. Write tests for bug reports

Slide 82

Slide 82 text

How do I get started? 2. Use TDD for new features

Slide 83

Slide 83 text

How do I get started? 3. Write unit tests for existing code you use while doing 1. and 2.

Slide 84

Slide 84 text

Next Steps Continuous Integration https://github.com/kmmbvnr/django-jenkins Jenkins

Slide 85

Slide 85 text

Thanks! @kevinharvey ! http://www.smilecareclub.com/ ! SmileCareClub is hiring! DM me.

Slide 86

Slide 86 text

Resources • https://github.com/kmmbvnr/django- jenkins • https://sites.google.com/site/kmmbvnr/ home/django-jenkins-tutorial • http://jenkins-ci.org/ Jenkins

Slide 87

Slide 87 text

Resources • http://funkload.nuxeo.org/ • http://jmeter.apache.org/ • http://httpd.apache.org/docs/2.2/ programs/ab.html Performance Testing

Slide 88

Slide 88 text

What about continuous integration?

Slide 89

Slide 89 text

What about continuous integration? I like Jenkins.

Slide 90

Slide 90 text

.WAR

Slide 91

Slide 91 text

WHAT

Slide 92

Slide 92 text

IS

Slide 93

Slide 93 text

IT

Slide 94

Slide 94 text

GOOD

Slide 95

Slide 95 text

FOR?

Slide 96

Slide 96 text

What about continuous integration? But in this case I’ll make an exception.

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

Configuring Jenkins $ wget http://mirrors.jenkins-ci.org/war/latest/jenkins.war $ java -jar jenkins.war ! http://localhost:8080

Slide 99

Slide 99 text

Configuring Jenkins You’ll need some plugins: - Jenkins Violations Plugin - Jenkins Git Plugin - Jenkins Cobertura Plugin

Slide 100

Slide 100 text

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”

Slide 101

Slide 101 text

Configuring Jenkins DONE

Slide 102

Slide 102 text

Using fixtures Manage data for tests runs

Slide 103

Slide 103 text

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" } } ]

Slide 104

Slide 104 text

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