|
|
@ -6,7 +6,7 @@ Chap2 introduced writing a functional test using unittest module (expected failu
|
|
|
|
|
|
|
|
|
|
|
|
* Django encourage structure the code into apps. We can reuse app developed by us or others
|
|
|
|
* Django encourage structure the code into apps. We can reuse app developed by us or others
|
|
|
|
|
|
|
|
|
|
|
|
Create an app `lists` (sub-directory) via `python manage.py startapp lists` along other `functional_tests.py` & `manage.py`, this subdir should include `tests.py` be default
|
|
|
|
Create an app `lists` (sub-directory) via `python manage.py startapp lists` in the directory which contains `functional_tests.py` & `manage.py`, this subdir should include `tests.py` be default
|
|
|
|
|
|
|
|
|
|
|
|
## 3.2 Unit Tests, and How They Differ from Functional Tests
|
|
|
|
## 3.2 Unit Tests, and How They Differ from Functional Tests
|
|
|
|
|
|
|
|
|
|
|
@ -47,11 +47,11 @@ python manage.py test
|
|
|
|
|
|
|
|
|
|
|
|
## 3.4 Django's MVC, URLs, and View Functions
|
|
|
|
## 3.4 Django's MVC, URLs, and View Functions
|
|
|
|
|
|
|
|
|
|
|
|
Django is structured along a classic [**Model-View-Controller (MVC)** pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
|
|
|
|
Django is structured along classic [**Model-View-Controller (MVC)** pattern](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
|
|
|
|
|
|
|
|
|
|
|
|
Django's main job is to decide what to do when a user asks for a particular URL on our site.
|
|
|
|
Django's main job is to decide what to do when a user asks for a particular URL on our site.
|
|
|
|
|
|
|
|
|
|
|
|
Django's workflow is like:
|
|
|
|
Django's workflow:
|
|
|
|
|
|
|
|
|
|
|
|
1. An HTTP `request` comes in for a particular URL
|
|
|
|
1. An HTTP `request` comes in for a particular URL
|
|
|
|
2. *Resolving the URL*: Django uses some rules to decide which `view` function should deal with the request.
|
|
|
|
2. *Resolving the URL*: Django uses some rules to decide which `view` function should deal with the request.
|
|
|
@ -62,9 +62,33 @@ Let's design a tests:
|
|
|
|
1. Verify we can resolve the URL for the root of site ("/") to a particular view function we've made?
|
|
|
|
1. Verify we can resolve the URL for the root of site ("/") to a particular view function we've made?
|
|
|
|
2. Verify view function return some HTML which will get the functional test to pass?
|
|
|
|
2. Verify view function return some HTML which will get the functional test to pass?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
from django.test import TestCase
|
|
|
|
|
|
|
|
from django.urls import resolve
|
|
|
|
|
|
|
|
from lists.views import home_page
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HomePageTest(TestCase):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_root_url_resolves_to_home_page_view(self):
|
|
|
|
|
|
|
|
found = resolve('/')
|
|
|
|
|
|
|
|
self.assertEqual(found.func, home_page)
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Where:
|
|
|
|
|
|
|
|
* The resolve() function if `django.url` package can be used for resolving URL paths to the corresponding view functions. The function returns a [`ResolverMatch` object](https://docs.djangoproject.com/en/3.1/ref/urlresolvers/#django.urls.ResolverMatch) that allows you to access various metadata about the resolved URL.
|
|
|
|
|
|
|
|
* In `assertEqual(...)`, we try to test that `found.func` is `home_page` (a view function we haven't defined yet)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Result of running test:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
ImportError: cannot import name 'home_page'
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 3.5 At Last! We Actually Write Some Application Code!
|
|
|
|
## 3.5 At Last! We Actually Write Some Application Code!
|
|
|
|
|
|
|
|
|
|
|
|
Add content in `lists/views.py`, to pass the test
|
|
|
|
In 3.4, we created a `test_root_url_resolves_to_home_page_view()` test case that check "django can resolve request to root and return a view function"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Hence, add content in `lists/views.py`, we can pass the test
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
```python
|
|
|
|
from django.shortcuts import render
|
|
|
|
from django.shortcuts import render
|
|
|
@ -72,10 +96,122 @@ from django.shortcuts import render
|
|
|
|
home_page = None
|
|
|
|
home_page = None
|
|
|
|
```
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Running the test generate following info
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
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 "/home/jason/HomeWorkstation/SynologyGiteaSpace/python-tdd-book-src/src/lists/tests.py", line 8, in test_root_url_resolves_to_home_page_view
|
|
|
|
|
|
|
|
found = resolve('/')
|
|
|
|
|
|
|
|
File "/home/jason/miniconda3/envs/python-tdd-book/lib/python3.6/site-packages/django/urls/base.py", line 27, in resolve
|
|
|
|
|
|
|
|
return get_resolver(urlconf).resolve(path)
|
|
|
|
|
|
|
|
File "/home/jason/miniconda3/envs/python-tdd-book/lib/python3.6/site-packages/django/urls/resolvers.py", line 394, in resolve
|
|
|
|
|
|
|
|
raise Resolver404({'tried': tried, 'path': new_path})
|
|
|
|
|
|
|
|
django.urls.exceptions.Resolver404: {'tried': [[<RegexURLResolver <RegexURLPattern list> (admin:admin) ^admin/>]], 'path': ''}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
----------------------------------------------------------------------
|
|
|
|
|
|
|
|
Ran 1 test in 0.001s
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* error due to `django.urls.exceptions.Resolver404: {'tried': [[<RegexURLResolver <RegexURLPattern list> (admin:admin) ^admin/>]], 'path': ''}`
|
|
|
|
|
|
|
|
* Happen in `python-tdd-book-src/src/lists/tests.py", line 8, in test_root_url_resolves_to_home_page_view`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Overall, the traceback can be interpreted as: "when trying to resolve `/`, Django raised a 404 error". i.e. Django can't find a URL mapping for "/"
|
|
|
|
|
|
|
|
|
|
|
|
## 3.6 urls.py
|
|
|
|
## 3.6 urls.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Django use `urls.py` in each app to map URLs to view functions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
urlpatterns = [
|
|
|
|
|
|
|
|
url(r'^admin/', admin.site.urls),
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Note:
|
|
|
|
|
|
|
|
* This book use Django v1.11. So `path` function is not utilized, it's used in v3.x
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Explain:
|
|
|
|
|
|
|
|
* `url` start with a regex, which defines which URLs it looks for, and where (function) should these request to send
|
|
|
|
|
|
|
|
* `^$` means empty string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Modifying `superlists.urls` to
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
from django.conf.urls import url
|
|
|
|
|
|
|
|
from lists import views
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
urlpatterns = [
|
|
|
|
|
|
|
|
url(r'^$', views.home_page, name='home'),
|
|
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Generate error as shown
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
...
|
|
|
|
|
|
|
|
File "/home/jason/HomeWorkstation/SynologyGiteaSpace/python-tdd-book-src/src/superlists/urls.py", line 20, in <module>
|
|
|
|
|
|
|
|
url(r'^$', views.home_page, name='home'),
|
|
|
|
|
|
|
|
File "/home/jason/miniconda3/envs/python-tdd-book/lib/python3.6/site-packages/django/conf/urls/__init__.py", line 85, in url
|
|
|
|
|
|
|
|
raise TypeError('view must be a callable or a list/tuple in the case of include().')
|
|
|
|
|
|
|
|
TypeError: view must be a callable or a list/tuple in the case of include().
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Analyzing error message "TypeError: view must be a callable or a list/tuple in the case of include().": unit tests have actually made the link btw the URL "/" and `home_page = None` in `lists/views.py`, and are now complaining that `home_page` view is not callable.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Fix this problem via modifying `lists/views.py`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
def home_page():
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 3.7 Unit Testing a View
|
|
|
|
## 3.7 Unit Testing a View
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
After creating simple `home_page` empty function. We can create test function in `lists/tests.py`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
def test_home_page_returns_correct_html(self):
|
|
|
|
|
|
|
|
request = HttpRequest()
|
|
|
|
|
|
|
|
response = home_page(request)
|
|
|
|
|
|
|
|
html = response.content.decode('utf8')
|
|
|
|
|
|
|
|
self.assertTrue(html.startswith('<html>'))
|
|
|
|
|
|
|
|
self.assertIn('<title>To-Do list</title>', html)
|
|
|
|
|
|
|
|
self.assertTrue(html.endswith('</html>'))
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Result of `python manager.py test` is:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
line 15, in test_home_page_returns_correct_html
|
|
|
|
|
|
|
|
response = home_page(request)
|
|
|
|
|
|
|
|
TypeError: home_page() takes 0 positional arguments but 1 was given
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.7.1 The Unit-Test/Code Cycle
|
|
|
|
### 3.7.1 The Unit-Test/Code Cycle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TDD *unit-test/code cycle*:
|
|
|
|
|
|
|
|
1. In the terminal, run unit tests and see how they fail
|
|
|
|
|
|
|
|
2. In the editor, create minimal code change to fix test failure.
|
|
|
|
|
|
|
|
3. Repeat
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Fix the `views.py` to
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
|
|
|
def home_page(request):
|
|
|
|
|
|
|
|
return HttpResponse('<html><title>To-Do list</title></html>')
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Conclusion of covering:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Starting a Django app
|
|
|
|
|
|
|
|
* The Django unit test runner
|
|
|
|
|
|
|
|
* The difference btw FTs and unit tests
|
|
|
|
|
|
|
|
* Django URL resolving and `urls.py`
|
|
|
|
|
|
|
|
* Django view functions, request and response objects
|
|
|
|
|
|
|
|
* And returning basic HTML
|