python-tdd-book/textbook/chap6.md

2.9 KiB

Chapter 6. Improving Functional Tests: Ensuring Isolation and Removing Voodoo Sleeps

6.1 Ensuring Test Isolation in Functional Tests

  • Problem left on scratchpad: Clean up after FT runs
  • Methods:
      1. In functional_tests.py add in codes to delete database
      1. Use class LiveServerTestCase (Django 1.4). It will create a test db and start up a dev server for functional tests

How to use LiveServerTestCase:

  • It's derived from Django's TestCase class, so can be run by Django test runner
  • Django test runner search for all scripts start with test

What to do?:

  1. Create a functional_test directory, i.e. create a functional_test app
  2. rename functional_test.py as tests.py and move it into functional_test directory
  3. Modify tests.py to let it use LiveServerTestCase as shown below, and instead of hardcoding visit to localhost port 8000, LiveserverTestCase provide attribute live_server_url.
class NewVisitorTest(LiveServerTestCase):
    ...

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

6.1.1 Running Just the Unit Tests

We can specify what we want to run by

python manage.py test functional_tests

or

python manage.py test lists

6.2 Aside: Upgrading Selenium and Geckodriver

Sometimes, mismatch between firefox and (Selenium + Geckodriver) may result failure in test, as firefox may auto upgrade itself at night. Upgrade Selenium + Geckodriver as follow:

  1. pip install --upgrade selenium
  2. Download new geckodriver

6.3 On Implicit and Explicit Waits, and Voodoo time.sleeps

Implicit Waits & Explicit Waits:

  • Implicit Waits: Selenium tries to wait "automatically" for you when it thinks the page is loading. But it's unstable, DON'T USE IT.
  • Explicit Waits: time.sleep(...) in FT. It's hard to set a fixed second (Voodoo Sleep). A while loop with try/except will be more better
from selenium.common.exceptions import WebDriverException

MAX_WAIT = 10 # Maximum amount of time we're prepared to wait
[...]

    def wait_for_row_in_list_table(self, row_text):
        start_time = time.time()
        while True:
            try:
                table = self.browser.find_element_by_id('id_list_table')
                rows = table.find_elements_by_tag_name('tr')
                self.assertIn(row_text, [row.text for row in rows])
                return
            except (AssertionError, WebDriverException) as e:
                if time.time() - start_time > MAX_WAIT:
                    raise e
                time.sleep(0.5)

Replace all check_for_row_in_list_table with wait_for_row_in_list_table

"BEST PRACTICES" Applied in this Chapter

  • Ensuring test isolation and managing global state
  • Avoid "voodoo" sleeps (i.e. time.sleep)
  • Don't rely on Selenium's implicit waits