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