python-tdd-book/textbook/chap4.md

4.9 KiB

Chapter 4. What Are We Doing with All These Tests? (And, Refactoring)

4.1 Programming is like pulling a bucket of water up from a well

TDD is discipline. Its payoffs not come immediately

4.2 Using selenium to test user interactions

Modify functional_tests.py to test user interactions

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time
import unittest

class NewVisitorTest(unittest.TestCase):

    def setUp(self):
        self.browser = webdriver.Firefox()

    def tearDown(self):
        self.browser.quit()

    def test_can_start_a_list_and_retrieve_it_later(self):
        # Edith has heard about a cool new online to-do app. She goes
        # to check out its homepage
        self.browser.get('http://localhost:8000')

        # She notices the page title and header mention to-do lists
        self.assertIn('To-Do', self.browser.title)
        header_text = self.browser.find_element_by_tag_name('h1').text
        self.assertIn('To-Do', header_text)

        # She is invited to enter a to-do item straight away
        inputbox = self.browser.find_element_by_id('id_new_item')
        self.assertEqual(
            inputbox.get_attribute('placeholder'),
            'Enter a to-do item'
        )

        # She types "Buy peacock feathers" into a text box (Edith's hobby
        # is tying fly-fishing lures)
        inputbox.send_keys('Buy peacock feathers')

        # When she hits enter, the page updates, and now the page lists
        # "1: Buy peacock feathers" as an item in a to-do list
        inputbox.send_keys(Keys.ENTER)
        time.sleep(1)
        table = self.browser.find_element_by_id('id_list_table')
        rows = table.find_element_by_tag_name('tr')
        self.assertTrue(
            any(row.text == '1: Buy peacock feathers' for row in rows)
        )

        # There is still a text box inviting her to add another item. She
        # enters "Use peacock feathers to make a fly" (Edith is very methodical)
        self.fail('Finish the test!')

        # The page updates again, and now shows both items on her list

        # Edith wonders whether the site will remember her list. Then she sees
        # that the site has generated a unique URL for her -- there is some
        # explanatory text to that effect.

        # She visits that URL - her to-do list is still there.

        # Satisfied, she goes back to sleep

if __name__ == '__main__':
    unittest.main(warnings='ignore')

Start django server and then run python functional_tests.py

4.3 The "Don't Test Constants" Rule, and Templates to the Rescue

Our object: test HTML.

Problem: currently test case look for exact HTML string, directly test content within HTML using fixed string is not wise.

Testing content within a HTML is not useful, as it's testing constant.

Unit tests are about testing logic, flow control, and configuration.

Using template is much better solution to test raw strings in Python.

4.3.1 Refactoring to Use a Template

Our object: refactor view function return the same HTML

Refactor = improve the code without changing its functionality

  1. Create a html file in lists/template folder, in which Django look for template that used for the app
  2. django use render function to render the template, given request
  3. Add app into superlists/settings.py's INSTALLED_APPS list, so django will search it

Check commit 89224c9

4.3.2 The Django Test Client

Django provides Django Test Client which can be used to check what templates are used.

        request = HttpRequest() #1
        response = home_page(request) #2

can be replaced by (django test client)

        response = self.client.get('/') #1

And assert methods

        self.assertTrue(html.startswith('<html>'))
        self.assertIn('<title>To-Do lists</title>', html)
        self.assertTrue(html.strip().endswith('</html>'))

can be replaced by

        self.assertTemplateUsed(response, 'home.html') #3

Note: DJANGO TEST CLIENT is introduced until now because manual test steps are necessary for learning

Check commit 747e0c7

4.4 On Refactoring

Tip: When refactoring, work on either the code or the tests, but not both at once, otherwise, changing both will quickly lead to change across multiple files and chaos

4.5 A Little More of Our Front Page

Recap: The TDD Process

Main aspects of TDD process:

  • Functional tests
  • Unit tests
  • The unit-test/code cycle
  • Refactoring

Overall TDD process is:

TDD workflow

Ques: How does FT, UT cooperate with TDD?

ANS: FT is a larger TDD cycle, while UTs are smaller TDD cycles

  1. Write a FT and see it fail.
  2. mini-TDD cycle of each unit test is employed to achieve small steps

QUES: How does refactoring cooperate with TDD?

ANS: Use a FT to check the behavior of app, FT is fixed but UTs can be add/removed

Overall, it's a "Double-Loop TDD" as shown below

Double-Loop TDD