python web development test driven method learning notes 1

Posted by jwb666 on Wed, 12 Jan 2022 12:24:37 +0100

Part I TDD and Django Foundation

Chapter 1 using functional testing to assist in the installation of Django

(1) Get Django running

  • Creating files: functional_tests.py
from selenium import webdriver

browser = webdriver.Chrome()

browser.get('http://localhost:8000')

assert 'Django' in browser.title
  • Create directory: Django admin startproject superlists
----functional_tests.py
----superlists
	---manage.py
	---superlists
		---__init__.py
		---settings.py
		---urls.py
		---wsgi.py
  • Running server: Python manage py runserver
  • Open another terminal window and run: python functional_tests.py

Run functional_ tests. The premise of Py is that the browser driver of selenium has been installed (there is only chrome in this machine, so download the corresponding driver, and you need to refer to the version of chrome to download)
View the version command of python Library: pip list;

Create git repository

# View files in the current directory
ls 
# Set functional_ tests. Move py to the super lists directory
mv functional_tests.py superlists/
#Enter the super lists directory
cd superlists
#Create warehouse
git init .
# view all files
ls
#Ignore dB SQLite3, which is not included in version control
echo "db.sqlite3" >> .gitignore
# Add additional files
git add .
# View status
git status
'''
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   .gitignore
        new file:   db.sqlite3
        new file:   functional_tests.py
        new file:   manage.py
        new file:   superlists/__init__.py
        new file:   superlists/__pycache__/__init__.cpython-36.pyc
        new file:   superlists/__pycache__/settings.cpython-36.pyc
        new file:   superlists/__pycache__/urls.cpython-36.pyc
        new file:   superlists/__pycache__/wsgi.cpython-36.pyc
        new file:   superlists/settings.py
        new file:   superlists/urls.py
        new file:   superlists/wsgi.py
'''
# Delete pyc file
# git rm --cached deletes the file from the index, but the local file still exists. You don't want this file to be versioned
git rm -r --cached superlists/__pycache__
echo "__pycache__" >> .gitignore
echo ".pyc" >> .gitignore
#Make the first submission
git commit
'''
Tips:
Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: unable to auto-detect email address (got 'sophia@sophia.(none)')
'''
# Modify global user information
git config --global user.name "sophia"
git config --global user.email sophia@example.com
'''
# View user name and mailbox name
git config user.name
git config user.email
'''
# In the pop-up editing window, enter the submission message: First commit: First FT and basic 
# Django config

Chapter 2 uses unittest module to expand function test

(1) Use of unittest module

  • In functional_tests.py write:
from selenium import webdriver
import unittest

class NewVisitorTest(unittest.TestCase):
	
	def setUp(self):
		self.browser = webdriver.Chrome()

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

	def test_can_start_a_list_and_retrieve_it_later(self):
		self.browser.get("http://localhost:8000")

		self.assertIn('To-Do',self.browser.title)
		self.fail('Finish the test!')


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

Running server: Python manage py runserver

In addition, open the terminal operation file: Python functional_ tests. py

Traceback (most recent call last):
  File "functional_tests.py", line 15, in test_can_start_a_list_and_retrieve_it_later
    self.assertIn('To-Do',self.browser.title)
AssertionError: 'To-Do' not found in 'Django: the Web framework for perfectionists with deadlines.'

Chapter 3 uses unit testing to test a simple home page

(1) First Django application, first unit test

Create a Django application:

python manage.py startapp lists

In lists / tests Py write:

class SmokeTest(TestCase):

	def test_bad_maths(self):
		self.assertEqual(1+1,3)

Run: Python manage py test

Traceback (most recent call last):
  File "D:\study-day-day-up!\Exercise items\TDDTest\ch01\superlists\lists\tests.py", line 8, in test_bad_maths
    self.assertEqual(1+1,3)
AssertionError: 2 != 3

View status and changes and submit:

git status
git add lists
git diff --staged
# git commit -m can write the submission information directly without editing in the editor
git commit -m "Add app for lists,with deliberately failing unit test"

(2) mvc,url and view functions in Django

Django test page purpose:

  • Can the url of the website path ("/") be parsed and mapped to a view function written
  • Can the view function return some html to pass the function test

First, test whether the directory can be resolved:
Open lists / tests Py, rewrite to:

from django.test import TestCase
# From Django core. urlresolvers import resolve
# Error no module name 'Django core. urlresolvers'
# Reason: django2 0 put the original Django core. The urlresolvers package was changed to Django URLs package, so we need to modify all the imported packages.
from django.urls import resolve
from lists.views import home_page

# Create your tests here.

'''
class SmokeTest(TestCase):

	def test_bad_maths(self):
		self.assertEqual(1+1,3)
'''

class HomePageTest(TestCase):

	def test_root_url_resolves_to_home_page_view(self):
		found = resolve('/')
		self.assertEqual(found.func,home_page)

Terminal results:

$ python manage.py test
ImportError: cannot import name 'home_page'

Fixed a problem: unable to read from lists Import home from views_ page
Solution: in lists / views Py write:

from django.shortchts import render

home_page = None

Run the test again:

$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_root_url_resolves_to_home_page_view (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\study-day-day-up!\Exercise items\TDDTest\ch01\superlists\lists\tests.py", line 17, in test_root_url_resolves_to_home_page_view
    found = resolve('/')
  File "D:\Anaconda3\envs\env1\lib\site-packages\django\urls\base.py", line 24, in resolve
    return get_resolver(urlconf).resolve(path)
  File "D:\Anaconda3\envs\env1\lib\site-packages\django\urls\resolvers.py", line 571, in resolve
    raise Resolver404({'tried': tried, 'path': new_path})
django.urls.exceptions.Resolver404: {'tried': [[<URLResolver <URLPattern list> (admin:admin) 'admin/'>]], 'path': ''}

----------------------------------------------------------------------
Ran 1 test in 0.013s

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

Error cause analysis: when trying to resolve "/", Django throws 404 error, that is, the URL mapping of "/" cannot be found.
Solution: in URLs Py file defines how to map URLs to view functions.
There are two configuration methods:

  • Directly at the root URLs Py file (superlists/superlists/urls.py)
from django.contrib import admin
from django.urls import path
from lists import views   #Import view file in application

urlpatterns = [
    #path('admin/', admin.site.urls),  #The background entry is not available for the time being. Please comment it out
	path('',views.home_page,name='home'),
]
  • Configure in application: create child URLs in application lists Py file:
from django.urls import path
from . import views

urlpatterns=[
	path('',views.home_page,name='home'),
]

Then import to the root URLs Py file:

from django.contrib import admin
from django.urls import path,include

urlpatterns = [
    #path('admin/', admin.site.urls),
	path('',include('lists.urls')),
]

Operation results:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database for alias 'default'...

It should be noted that Django 2 X is slightly different from the previous version in the configuration path. For details, see the blog:
https://blog.csdn.net/qq_41100991/article/details/100183818

Finally, submit version information:

# Add changes in all tracked files and use the commit information entered on the command line
git commit -am "First unit test url mapping,dummy view"

Next, write unit tests for the view.
Unit test process:

  • Run unit tests (python manage.py test) on the terminal to see how they fail;
  • Change the minimum amount of code in the encoder to pass the current failed test.
    Then repeat.

Open lists / tests Py, add a new test method.

from django.urls import resolve  #There is a discrepancy between this and the book, which has been mentioned earlier
from django.test import TestCase
from django.http import HttpRequest

from lists.views import home_page

class Home PageTest(TestCase):
	
	def test_root_url_resolves_to_home_page_view(self):
		found = resolve('/')
		self.assertEqual(found.func,home_page)

	def test_home_page_returns_correct_html(self):
		request = HttpRequest()
		response = home_page(request)
		self.assertTrue(response.content.startswith(b'<html>'))
		self.assertIn(b'<title>To-Do lists</title>',response.content)
		self.assertTrue(response.content.endswith(b'</html>'))

Run the unit test (python manage.py test) and get the results:

TypeError: home_page() takes 0 positional arguments but 1 was given

Change the code lists / views py:

def home_page(request):
	pass

Run test:

    self.assertTrue(response.content.startwith(b'<html>'))
AttributeError: 'NoneType' object has no attribute 'content'

Continue to modify, using Django http. HttpResponse:

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
def home_page(request):
	return HttpResponse()

Rerun the test:

    self.assertTrue(response.content.startswith(b'<html>'))
AssertionError: False is not true

Re write the code:

def home_page(request):
	return HttpResponse('<html>')

Operation results:

    self.assertIn(b'<title>To-Do lsit</title>',response.content)
AssertionError: b'<title>To-Do lsit</title>' not found in b'<html>'

Write code:

def home_page(request):
	return HttpResponse('<html><title>To-Do lists</title>')

Run test:

    self.assertTrue(response.content.endswith(b'</html>'))
AssertionError: False is not true

Modify again:

def home_page(request):
	return HttpResponse('<html><title>To-Do lists</title></html>')

Test results:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.004s

OK
Destroying test database for alias 'default'...

Now perform a functional test. If you shut down the development server, remember to start it.

$ python functional_tests.py
F
======================================================================
FAIL: test_can_start_a_list_and_retrieve_it_later (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "functional_tests.py", line 17, in test_can_start_a_list_and_retrieve_it_later
    self.fail('Finish the test!')
AssertionError: Finish the test!

----------------------------------------------------------------------
Ran 1 test in 3.717s

FAILED (failures=1)

At this time, a web page is successfully written (enter http://localhost:8000/ , view the web page source code, and home_ The html code passed in page is the same).
Submit version information:

$ git diff  #Test. Is displayed Py, and views View in PY
$ git commit -am"Basic view now returns minimal HTML"
$ git log --oneline #View progress of submitted information
c28336e (HEAD -> main) Basic view now returns minimal HTML
cdce47d First unit test url mapping,dummy view
2f0bf75 Add app for lists,with deliberately failing unit test
ba11276 Write the first functional test of the specification using annotations, and use unittest
ee94bbd First commit:First FT and basic Django config

Chapter 4: what's the use of writing these tests

Open functional_tests.py file to expand the function test:

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

class NewVisitorTest(unittest.TestCase):
	
	def setUp(self):
		self.browser = webdriver.Chrome()
		self.browser.implicitly_wait(3)

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

	def test_can_start_a_list_and_retrieve_it_later(self):
		self.browser.get("http://localhost:8000")

		self.assertIn('To-Do',self.browser.title)
		header_text = self.browser.find_element_by_tag_name('h1').text
		self.assertIn('To-Do',header_text)

		inputbox = self.browser.find_element_by_id('id_new_item')
		self.assertEqual(
			inputbox.get_attribute('placeholder'),
			'Enter a to-do item'
		)

		inputbox_send_keys('Buy peacock feathers')

		inputbox.send_keys(Keys.ENTER)

		table = self.browser.find_element_by_id('id_list_table')
		rows = table.find_elements_by_tag_name('tr')
		self.assertTrue(
			any(row.text == '1:Buy peacock feathers' for row in rows)
		)

		self.fail('Finish the test!')

Run function test:

$ python manage.py functional_tests.py
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"h1"}
  (Session info: chrome=97.0.4692.71)

Submit version information:

git diff
git commit -am"Functional test now checks we can input a to-do item"

Take a look at lists / tests Py. Generally speaking, unit tests "do not test constants", while testing html in text form is largely a test constant. In addition, a better way to insert original characters into python code is to use templates (put html in html files).
Refactoring with templates
Refactoring: improve the code without changing the function
Refactoring needs to check whether the test can pass:

python manage.py test
  • Extract the html string and write it to a separate file: create the directory lists/templates and create a new file home html, and then write html to this file;
  • Modify view function:
def home_page(request):
	'''
	render The first parameter is the request object, and the second parameter is the name of the rendered template, Django Will automatically search all app directories for the name templates Then build a folder based on the template content HttpResponse object
	'''
	return render(request,'home.html')

To see if the template works:

ERROR: test_home_page_returns_correct_html (lists.tests.HomePageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\study-day-day-up!\Exercise items\TDDTest\ch01\superlists\lists\tests.py", line 23, in test_home_page_returns_correct_html
    response = home_page(request)
  File "D:\study-day-day-up!\Exercise items\TDDTest\ch01\superlists\lists\views.py", line 6, in home_page
    return render(request,'home.html')
  File "D:\Anaconda3\envs\env1\lib\site-packages\django\shortcuts.py", line 36, in render
    content = loader.render_to_string(template_name, context, request, using=using)
  File "D:\Anaconda3\envs\env1\lib\site-packages\django\template\loader.py", line 61, in render_to_string
    template = get_template(template_name, using=using)
  File "D:\Anaconda3\envs\env1\lib\site-packages\django\template\loader.py", line 19, in get_template
    raise TemplateDoesNotExist(template_name, chain=chain)
django.template.exceptions.TemplateDoesNotExist: home.html

----------------------------------------------------------------------
Ran 2 tests in 0.017s

Error reason: unable to find template (list application is not officially registered in Django)
Solve the problem and open settings Py file, add lists:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
	'lists',
]

At this point, the test passes.
Note: some text editors will add an empty line to the last line of the file, which will cause the last assertion to fail. You can use lists / tests Py:

self.assertTrue(response.content.strip().endswith(b'</html>'))

Check whether the correct template is rendered. You can also use render, another helper function in Django_ to_ string:

from django.template.loader import render_to_string
[...]

	def test_home_page_returns_correct_html(self):
		request = HttpRequest()
		response = home_page(request)
		expected_html = render_to_string('home.html')
		# . decode() response The bytes in content are converted to Unicode strings in python
		self.assertEqual(response.content.decode(),expected_html)

Submit once after Refactoring:

git status
git add .
git diff --staged
git commit -m"Refactor home page view to use a template"

At this time, the function test still fails. Modify the code and let it pass.
First you need a

Element (home.html file):

<html>
	<head>
		<title>To-Do lists</title>
	</head>
	<body>
		<h1>Your To-Do list</h1>
	</body>
</html>

Run a function test to see if you agree with this modification:

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="id_new_item"]"}
  (Session info: chrome=97.0.4692.71)

Continue to modify:

[...]
		<h1>Your To-Do list</h1>
		<input id="id_new_item"/>
[...]

Now there are:

AssertionError: '' != 'Enter a to-do item'

Add placeholder text:

		<input id="id_new_item" placeholder="Enter a to-do item"/>

Results obtained:

selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"[id="id_list_table"]"}

Add a table to the page:

		<table id="id_list_table">
		</table>

Test results:

  File "functional_tests.py", line 34, in test_can_start_a_list_and_retrieve_it_later
    any(row.text == '1:Buy peacock feathers' for row in rows)
AssertionError: False is not true

Error analysis: no explicit failure message is provided - > custom error message is passed to the assertTrue method (functional_tests.py):

		self.assertTrue(
			any(row.text == '1:Buy peacock feathers' for row in rows),"New to-do item did not appear in table"
		)

Operation results:

AssertionError: False is not true : New to-do item did not appear in table

Submit:

git diff
git commit -am"Front page HTML now generated from a template"

TDD (Test Driven Development) overall process summary:

If there are both functional and unit tests:

—TO BE CONTINUE

Topics: Python Django