[PYTHON] Test Driven Development with Django Part 6

Test Driven Development with Django Part 6

This is a learning note to help you understand Test Driven Development (TDD) in Django.

References are [** Test-Driven Development with Python: Obey the Testing Goat: Using Django, Selenium, and JavaScript (English Edition) 2nd Edition **](https://www.amazon.co.jp/dp/B074HXXXLS We will proceed with learning based on / ref = dp-kindle-redirect? _ Encoding = UTF8 & btkr = 1).

In this book, we are conducting functional tests using Django 1.1 series and FireFox, but this time we will carry out functional tests on Djagno 3 series and Google Chrome. I've also made some personal modifications (such as changing the Project name to Config), but there are no major changes.

⇒⇒ Click here for Part 1 --Chapter 1 ⇒⇒ Click here for Part 2-Chapter 2 ⇒⇒ Click here for Part 3-Chapter 3 ⇒⇒ Click here for Part 4-Chapter 4 ⇒⇒ Click here for Part 5 --Chapter 5

Part1. The Basics of TDD and Django

Chapter6. Improving Functional Testss: Ensuring Isolation and Removing Voodoo Sleeps

In Chapter 5, I checked whether the POSTed data was saved and whether it could be returned to the response without any problem.

Since the unit test creates a Django test DB, the test data is deleted when the test is executed, but the functional test uses the production DB (db.sqlite3) (in the current setting). There was a problem that the data at the time of testing was also saved.

This time we will practice best practice for these problems.

Ensuring Test Isolation in Functional Tests

If the test data remains, it cannot be separated between the tests, so troubles such as "the test that should succeed because the test data is saved fails" occur. It is important to be aware of the separation between tests to avoid this.

In Djagno, you can implement a mechanism that automatically creates a database for testing like a unit test and deletes it after the test is completed by using the LiveServerTestCase class.

LiveServerTestCase is intended for testing with Django's test runner. When Django's test runner runs, it runs files starting with test in all folders.

So let's create a folder for functional testing like a Django application.

#Create folder
$ mkdir functional_tests
#To make Django recognize it as a Python package
$ type nul > functional_tests/__init__.py
#Rename and move existing functional tests
$ git mv functional_tests.py functional_tests/tests.py
#Verification
$ git status

Now I was running a functional test with python manage.py functional_tests.py You can now run it with python manage.py test functional_tests.

Now let's rewrite the functional test.

# django-tdd/functional_tests/tests.py

from django.test import LiveServerTestCase  #add to
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import time


class NewVisitorTest(LiveServerTestCase):  #Change

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

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

    def check_for_row_in_list_table(self, row_text):
        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])

    def test_can_start_a_list_and_retrieve_it_later(self):
        #Nobita is a new to-I heard that there is a do app and accessed the homepage.
        self.browser.get(self.live_server_url)  #Change

        #Nobita has the page title and header to-I confirmed that it suggests that it is a do app.
        self.assertIn('To-Do', self.browser.title)
        header_text = self.browser.find_element_by_tag_name('h1').text
        self.assertIn('To-Do', header_text)

        #Nobita is to-Prompted to fill in the do item,
        inputbox = self.browser.find_element_by_id('id_new_item')
        self.assertEqual(
            inputbox.get_attribute('placeholder'),
            'Enter a to-do item'
        )

        #Nobita wrote in the text box "Buy Dorayaki"(His best friend loves dorayaki)
        inputbox.send_keys('Buy dorayaki')

        #When Nobita presses enter, the page is refreshed
        # "1:Buying dorayaki"Is to-Found to be added as an item to the do list
        inputbox.send_keys(Keys.ENTER)
        time.sleep(3)  #Wait for page refresh.
        self.check_for_row_in_list_table('1: Buy dorayaki')

        #The text box allows you to continue to fill in items, so
        #Filled in "Billing Dorayaki Money"(He is tight on money)
        inputbox = self.browser.find_element_by_id('id_new_item')
        inputbox.send_keys("Demand payment for the dorayaki")
        inputbox.send_keys(Keys.ENTER)
        time.sleep(3)

        #The page was refreshed again and I was able to see that new items were added
        self.check_for_row_in_list_table('2: Demand payment for the dorayaki')

        #Nobita is this to-I was wondering if the do app was recording my items properly,
        #When I checked the URL, I found that the URL seems to be a specific URL for Nobita
        self.fail("Finish the test!")

        #When Nobita tried to access a specific URL that he had confirmed once,

        #The item was saved so I was happy to fall asleep.

Changed the functional test to inherit LiveServerTestCase from the ʻunittest module. Now that you can run functional tests using Django's test runners, I've removed the following: ʻif __name =='__main__'

Now let's actually run the functional test.

$ python manage.py test functional_tests

Creating test database for alias 'default'...
System check identified no issues (0 silenced).

======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (functional_tests.tests.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:--your_path--\django-TDD\functional_tests\tests.py", line 59, in test_can_start_a_list_and_retrieve_it_later
    self.fail("Finish the test!")
AssertionError: Finish the test!

----------------------------------------------------------------------
Ran 1 test in 28.702s

FAILED (failures=1)
Destroying test database for alias 'default'...

The functional test ended with self.fail and gave the same results as before applying the LiveServerTestCase. We also confirmed that a database for functional testing was created and deleted as soon as the test was completed.

Let's commit here.

$ git status
$ git add functional_tests
$ git commit -m "make functional_tests an app, use LiveSeverTestCase"

Running Just the Unit Tests

The python manage.py test command allows Django to run unit and functional tests together. If you want to test only unit tests, specify the application like python manage.py test lists.

On Implicit and Explicit Waits, and Voodoo time.sleeps

I added time.sleep (3) to check the result of running the functional test. Whether this time.sleep (3) is set to 3 seconds, 1 second, or 0.5 seconds depends on the response, but I do not know what is the correct answer.

Let's rewrite the functional test so that only the necessary buffers are prepared. Change check_for_row_in_list_table to wait_for_row_in_list_table and add polling / retry logic.

# django-tdd/functional_tests/tests.py

from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import WebDriverException  #add to
import time

MAX_WAIT = 10  #add to


class NewVisitorTest(LiveServerTestCase):

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

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

    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)
    [...]

This allows us to stop processing only the buffers we need for the response (we'll refactor it later). I try to wait up to 10 seconds.

Let's change the part that was executing check_for_row_in_list_table to wait_for_row_in_list_table and delete time.sleep (3). As a result, the current functional test looks like this:

# django-tdd/functional_tests/tests.py

from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import WebDriverException  #add to
import time

MAX_WAIT = 10  #add to


class NewVisitorTest(LiveServerTestCase):  #Change

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

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

    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)

    def test_can_start_a_list_and_retrieve_it_later(self):
        #Nobita is a new to-I heard that there is a do app and accessed the homepage.
        self.browser.get(self.live_server_url)  #Change

        #Nobita has the page title and header to-I confirmed that it suggests that it is a do app.
        self.assertIn('To-Do', self.browser.title)
        header_text = self.browser.find_element_by_tag_name('h1').text
        self.assertIn('To-Do', header_text)

        #Nobita is to-Prompted to fill in the do item,
        inputbox = self.browser.find_element_by_id('id_new_item')
        self.assertEqual(
            inputbox.get_attribute('placeholder'),
            'Enter a to-do item'
        )

        #Nobita wrote in the text box "Buy Dorayaki"(His best friend loves dorayaki)
        inputbox.send_keys('Buy dorayaki')

        #When Nobita presses enter, the page is refreshed
        # "1:Buying dorayaki"Is to-Found to be added as an item to the do list
        inputbox.send_keys(Keys.ENTER)
        self.wait_for_row_in_list_table('1: Buy dorayaki')

        #The text box allows you to continue to fill in items, so
        #Filled in "Billing Dorayaki Money"(He is tight on money)
        inputbox = self.browser.find_element_by_id('id_new_item')
        inputbox.send_keys("Demand payment for the dorayaki")
        inputbox.send_keys(Keys.ENTER)

        #The page was refreshed again and I was able to see that new items were added
        self.wait_for_row_in_list_table('2: Demand payment for the dorayaki')

        #Nobita is this to-I was wondering if the do app was recording my items properly,
        #When I checked the URL, I found that the URL seems to be a specific URL for Nobita
        self.fail("Finish the test!")

        #When Nobita tried to access a specific URL that he had confirmed once,

        #The item was saved so I was happy to fall asleep.

When I ran the functional test, it ended with self.fail ("Finish the test!") AssertionError: Finish the test!.

Testing "Best practives" applied in this chapter

Here is a summary of best practices in Chapter 6.

--A test must not affect other tests. Django's test runner creates and deletes a database for testing, so we'll use it for functional testing as well.

--Avoid abuse of time.sleep () It is easy to put time.sleep () and have a buffer for loading, but depending on the processing and buffer, a meaningless error may occur. avoid.

--Do not use Selenium's waits function It seems that selenium has a function to have a buffer automatically, but "Explicit is better than implict" and Zen of Python have it. Explicit implementation is preferred.

Recommended Posts

Test Driven Development with Django Part 3
Test Driven Development with Django Part 4
Test Driven Development with Django Part 6
Test Driven Development with Django Part 2
Test Driven Development with Django Part 1
Test Driven Development with Django Part 5
Test Driven Development Startup with PySide & Pytest
Development digest with Django
Django test
[Test Driven Development (TDD)] Chapter 21 Summary
How to authenticate with Django Part 2
How to authenticate with Django Part 3
Application development using SQLite with Django (PTVS)
Django begins part 1
Experience Part I "Multinational Currencies" in the book "Test Driven Development" in Python
Internationalization with django
Django begins part 4
CRUD with Django
First Django development
Create test data like that with Python (Part 1)
Build Django + NGINX + PostgreSQL development environment with Docker
Build the fastest Django development environment with docker-compose
[Python] Build a Django development environment with Docker
Django Getting Started Part 2 with eclipse Plugin (PyDev)
Build a Django development environment with Doker Toolbox
Authenticate Google with Django
Django 1.11 started with Python3.6
Upload files with Django
Strengthen with code test ⑦
Output PDF with Django
Strengthen with code test ⑨
Strengthen with code test ③
Markdown output with Django
Use Gentelella with django
Twitter OAuth with Django
Strengthen with code test ⑤
Strengthen with code test ④
Getting Started with Django 1
Primality test with python
Send email with Django
sandbox with neo4j part 10
Web application made with Python3.4 + Django (Part.1 Environment construction)
File upload with django
Strengthen with code test ②
Use LESS with Django
Pooling mechanize with Django
Use MySQL with Django
Strengthen with code test ①
Build a development environment with Poetry Django Docker Pycharm
[Memo] Django development environment
Articles that enable system development with Django (Python) _Introduction
Start today with Django
Getting Started with Django 2
Strengthen with code test ⑧
Strengthen with code test ⑨
Tutorial for doing Test Driven Development (TDD) in Flask-2 Decorators
Build a Django development environment with Docker! (Docker-compose / Django / postgreSQL / nginx)
[Memo] Build a development environment for Django + Nuxt.js with Docker
[Django] Build a Django container (Docker) development environment quickly with PyCharm
Build a bulletin board app from scratch with Django. (Part 2)
Build a bulletin board app from scratch with Django. (Part 3)