ทำการสร้าง
directory “functional_tests”
ซึ่งภายในจะมีไฟล์
"__init__.py”
ด้วยคำสั่ง
$ mkdir
functional_tests
$ touch
functional_tests/__init__.py
และทำการ
move functional tests
เข้าไปใน
directory
ที่เราสร้างขึ้น
และเปลี่ยนชื่อเป็น tests.py
ด้วยคำสั่ง
$ git mv
functional_tests.py functional_tests/tests.py
$ git
status
เมื่อรัน
tree
จะได้
.
├── db.sqlite3
├── functional_tests
│ ├── __init__.py
│ └── tests.py
├── lists
│ ├── admin.py
│ ├── __init__.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_item_text.py
│ │ ├── __init__.py
│ │ └── __pycache__
│ ├── models.py
│ ├── __pycache__
│ ├── templates
│ │ └── home.html
│ ├── tests.py
│ └── views.py
├── manage.py
└── superlists
├── __init__.py
├── __pycache__
├── settings.py
├── urls.py
└── wsgi.py
จากนั้นทำการ
แก้ไข functional_tests/tests.py
และทำการเปลี่ยน
NewVisitorTest class
เป็น
LiveServerTestCase
from django.test import LiveServerTestCase
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class NewVisitorTest(LiveServerTestCase):
def setUp(self):
[…]
จากนั้นทำการเปลี่ยน
localhost เป็น
live_server_url
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)
ทดสอบรัน FT
ทำการ commit ไว้
$ git status #
functional_tests.py renamed + modified, new __init__.py
$ git add functional_tests
$ git diff --staged -M
$ git
commit # msg eg "make functional_tests an app, use
LiveServerTestCase"
จากนั้นทำการรัน Unit Test
และเราสามารถรันเฉพาะบางส่วนได้
ยกตัวอย่างให้รัน lists
Useful Commands Updated
To run the functional tests
python3 manage.py test functional_tests
To run the unit tests
python3 manage.py test lists
ทำการเข้าไปเพิ่มในส่วนท้ายของ
ไฟล์ functional_tests/tests.py ในส่วน
def
test_can_start_a_list_and_retrieve_it_later(self): ว่า
# Edith wonders whether the site will remember her list. Then she sees
# that the site has generate a unique URL for her -- there is some
# explanatory text to that effect.
self.fail('Finish the test!')
# She visits that URL - her to-do list is still there.
# Satisfied, she goes back to sleep
REST
Idea ของ
data structure คือ
Model-View-Controller (MVC) สำหรับในแต่ละ
lists และ
lists item ก็จะมี
URL ของมันเอง
/lists/<list identifier>/
ถ้าต้องการสร้าง
list ใหม่
ก็จะมี URL พิเศษที่มันสามารถ
accept POST request ได
/lists/new
ถ้าต้องการสร้าง
list item ใหม่จาก
list ที่มีอยู่แล้ว
เราจะมีการ separate URL
ซึ่งเราจะสามารถส่ง
POST request ได้
/lists/<list identifier>/add_item
**เราแค่จะใช้แนวคิดของ
REST มาใช้**
Implementing
the New Design Using TDD
ให้มองหา
inputbox.send_keys('Buy peacock
feathers') ใน
functional_tests/tests.py. แล้วทำการแก้ไขดังนี้
inputbox.send_keys('Buy peacock feathers')
# When she hits enter, she is taken to a new URL,
# and now the page lists "1: Buy peacock feathers" as an item in a
# to-do list table
inputbox.send_keys(Keys.ENTER)
edith_list_url = self.browser.current_url
self.assertRegex(edith_list_url,'/lists/.+')
self.check_for_row_in_list_table('1: Buy peacock feathers')
# There is still a text box inviting her to add another item. She
[...]
และแก้ไขเพิ่มเติม
[...]
# The page updates again, and now shows both items on her list
self.check_for_row_in_list_table('2: Use peacock feathers to make a fly')
self.check_for_row_in_list_table('1: Buy peacock feathers')
# Now a new user, Francis, comes along to the site.
## We use a new browser session to make sure that no information
## of Edith's is coming through from cookies etc
self.browser.quit()
self.browser = webdriver.Firefox()
# Francis visits the home page. There is no sign of Edith's
# list
self.browser.get(self.live_server_url)
page_text = self.browser.find_element_by_tag_name('body').text
self.assertNotIn('Buy peacock feathers', page_text)
self.assertNotIn('make a fly', page_text)
# Francis starts a new list by entering a new item. He
# is less interesting than Edith...
inputbox = self.browser.find_element_by_id('id_new_item')
inputbox.send_keys('Buy milk')
inputbox.send_keys(Keys.ENTER)
# Francis gets his own unique URL
francis_list_url = self.browser.current_url
self.assertRegex(francis_list_url, '/lists/.+')
self.assertNotEqual(francis_list_url, edith_list_url)
# Again, there is no trace of Edith's list
page_text = self.browser.find_element_by_tag_name('body').text
self.assertNotIn('Buy peacock feathers', page_text)
self.assertIn('Buy milk', page_text)
# Satisfied, they both go back to sleep
และทดลองรัน FT
Commit ไว้ก่อน
$ git commit -a
Iterating
Towards the New Design
ทำการเปลี่ยนแปลง
locationใน
lists/tests.py หาในส่วนของ test_home_page_redirects_after_POST
self.assertEqual(response.status_code, 302)
self.assertEqual(response['location'], '/lists/the-only-list-in-the-world/')
และทดลองรัน UT
ให้ทำการเข้าไปแก้ไข home_page redirect ที่ lists/views.py.
def home_page(request):
if request.method == 'POST':
Item.objects.create(text=request.POST['item_text'])
return redirect('/lists/the-only-list-in-the-world/')
items = Item.objects.all()
return render(request, 'home.html', {'items': items})
เมื่อรัน
Unit Test จะ
OK แต่ถ้ารัน
FT จะพบว่า
URL /the-only-list-in-the-world/
ไม่ได้มีอยู่จริง
Testing
Views, Templates, and URLs Together with the Django Test Client
A
New Test Class
ให้ทดลอง
test client โดยเปิด
lists/tests.py แล้ว
copy
method test_home_page_displays_all_
list_items จาก HomePageTest แล้วทำการเปลี่ยนแปลงค่าเล็กน้อย
class ListViewTest(TestCase):
def test_displays_all_items(self):
Item.objects.create(text='itemey 1')
Item.objects.create(text='itemey 2')
response = self.client.get('/lists/the-only-list-in-the-world/')
self.assertContains(response, 'itemey 1')
self.assertContains(response, 'itemey 2')
ทดลอง รัน UT
จะพบว่า
404 != 200 ซึ่งเราจะต้องไปเพิ่มในส่วนของ
URLs
A
New URL
เข้าไปที่
superlists/urls.py. แล้วทำการแก้ไขเพิ่มเติม
urlpatterns = patterns('',
url(r'^$', 'lists.views.home_page', name='home'),
url(r'^lists/the-only-list-in-the-world/$', 'lists.views.view_list',
name='view_list'
),
# url(r'^admin/', include(admin.site.urls)),
)
รัน
Unit Test อีกครั้ง
จะพบว่า
จะต้องมี view function เพิ่มเติมด้วย
A
New View Function
ทำการสร้าง
dummy view function ใน
lists/views.py:
def view_list(request):
pass
ถ้าพอจำได้
การส่งค่า pass ธรรมดา
จะรันไม่ได้ เพราะต้องการ
HttpRespone ให้ทำการเพิ่มเติมส่วน
return
def view_list(request):
items = Item.objects.all()
return render(request, 'home.html', {'items': items})
ทำการรัน
Unit Test
และทดลองรัน
FT
Green?
Refactor
รันคำสั่ง
$
grep -E "class|def" lists/tests.py
และเข้าไปทำการลบ
test_home_page_displays_all_list_items
method ใน lists/tests.py
และทดลองรัน
manage.py test lists
A
Separate Template for Viewing Lists
ทดลองรัน Test ใหม่เพื่อตรวจสอบการใช้งาน template และให้เข้าไปเพิ่มเติมใน
lists/views.py.
class ListViewTest(TestCase):
def test_uses_list_template(self):
response = self.client.get('/lists/the-only-list-in-the-world/')
self.assertTemplateUsed(response, 'list.html')
def test_displays_all_items(self):
[...]
เมื่อทดลองรันทดสอบ
assert จะได้ว่า
AssertionError: False is not true : Template 'list.html' was not a template
used to render the response. Actual template(s) used: home.html
และให้เข้าไปแก้ไขใน
lists/views.py. อีกที
def view_list(request):
items = Item.objects.all()
return render(request, 'list.html', {'items': items})
ทดลองรัน
UT จะได้
django.template.base.TemplateDoesNotExist: list.html
จากนั้นให้เข้าไปทำการสร้างไฟล์ใหม่
ที่ lists/templates/list.html
$ touch lists/templates/list.html
และทำการ
copy
ไฟล์
home.html
ไปเป็น
list.html
cp lists/templates/home.html lists/templates/list.html
ทดลองรัน
UT
จะเห็นว่ารันผ่าน
และทำการเปลี่ยนแปลงภายในไฟล์
lists/templates/home.html.
<body>
<h1>Start a new To-Do list</h1>
<form method="POST">
<input name="item_text" id="id_new_item" placeholder="Enter a to-do item" />
{% csrf_token %}
</form>
</body>
และทดลองรัน
UT อีกครั้ง
ก็ยังผ่านเหมือนเดิม
ให้เข้าไปแก้ไขใน lists/views.py.
def home_page(request):
if request.method == 'POST':
Item.objects.create(text=request.POST['item_text'])
return redirect('/lists/the-only-list-in-the-world/')
return render(request, 'home.html')
รัน
UT อีกครั้งก็จะยัง
รันผ่านเหมือนเดิม และให้ทดลองรัน
FT
เรายังติดปัญหาที่
input ตัวที่
2 สามารถแก้ไขได้โดยเข้าไปที่
lists/templates/list.html:
<form method="POST" action="/">
และทดลองรัน
FT อีกครั้ง
โอเค
เราได้ URL ที่ไม่ซ้ำกันแล้วสำหรับ
1 list ของเรา
และต่อไปให้ทำการ git
commit ไว้ก่อน
$ git status # should show 4 changed files and 1 new file, list.html
$ git add lists/templates/list.html
$ git diff # should show we've simplified home.html,
# moved one test to a new class in lists/tests.py added a new view
# in views.py, and simplified home_page and made one addition to
# urls.py
$ git commit -a # add a message summarising the above, maybe something like
# "new URL, view and template to display lists"
Another URL and View for Adding List Items
A Test Class for New List Creation
เปิด
lists/tests.py,
แล้วทำการเพิ่มเติม
class
class
NewListTest(TestCase):
def
test_saving_a_POST_request(self):
self.client.post(
'/lists/new',
data={'item_text':
'A new list item'}
)
self.assertEqual(Item.objects.count(),
1)
new_item =
Item.objects.first()
self.assertEqual(new_item.text,
'A new list item')
def
test_redirects_after_POST(self):
response =
self.client.post(
'/lists/new',
data={'item_text':
'A new list item'}
)
self.assertEqual(response.status_code,
302)
self.assertEqual(response['location'],
'/lists/the-only-list-in-the-world/')
A URL and View for New List Creation
เปิด
superlists/urls.py.
และแก้ไข
urlpatterns
= patterns('',
url(r'^$',
'lists.views.home_page', name='home'),
url(r'^lists/the-only-list-in-the-world/$',
'lists.views.view_list',
name='view_list'
),
url(r'^lists/new$',
'lists.views.new_list',
name='new_list'),
# url(r'^admin/',
include(admin.site.urls)),
)
เปิด
lists/views.py.
และแก้ไข
def
new_list(request):
return
redirect('/lists/the-only-list-in-the-world/')
จะได้ว่า
เข้าไป
lists/views.py.
และแก้ไขอีกครั้ง
def
new_list(request):
Item.objects.create(text=request.POST['item_text'])
return
redirect('/lists/the-only-list-in-the-world/')
จะพบว่า
AssertionError:
'http://testserver/lists/the-only-list-in-the-world/' !=
'/lists/the-only-list-in-the-world/'
-
http://testserver/lists/the-only-list-in-the-world/
? -----------------
+
/lists/the-only-list-in-the-world/
ให้เข้าไปแก้ไขที่
lists/tests.py.
เปลี่ยนเป็น
AssertRedirect
def
test_redirects_after_POST(self):
response =
self.client.post(
'/lists/new',
data={'item_text':
'A new list item'}
)
self.assertRedirects(response,
'/lists/the-only-list-in-the-world/')
ผลที่ได้
Ran 8 tests in 0.018s
OK
Removing Now-Redundant Code and Tests
เราสามารถ
remove
if request.method ==
'POST' ได้หรือเปล่า
(
สิ่งที่ซับซ้อน)
เข้าไปใน
lists/views.py.
def
home_page(request):
return
render(request, 'home.html')
ผลที่ได้
Ran 8 tests in 0.017s
OK
และเรายังสามารถ
remove
test_home_page_only_saves_
items_when_necessary test
ได้อีกด้วย
ผลที่ได้
Ran 7 tests in 0.016s
OK
Pointing Our Forms at the New URL