คำสำคัญที่พูดถึงในหนังสือ TDD with Python
Big Design Up Front (BDUF)
เป็นแนวทางการพัฒนา software ที่จะมีการออกแบบโปรแกรมให้เสร็จสิ้นก่อนจะเริ่มสร้างโปรแกรมขึ้นมา ซึ่งเกี่ยวข้องกับการพัฒนา software แบบ "Waterfall Model"(ซึ่งตามหนังสืออธิบายว่าแนวทางของ TDD นั้นตรงข้ามกับ BDUF คือเป็นการพัฒนาที่มีความคล่องตัว นั่นคือการแก้ไขปัญหาจากการเขียนโค้ดและให้ได้โค้ดที่สามารถใช้งานจริงได้ในระดับนึง แล้วพัฒนาโปรแกรมนั้นตามการใช้งานจริงของ User (พัฒนาตาม feedback ของ User))
ศึกษาข้อมูลเพิ่มเติม :
http://en.wikipedia.org/wiki/Big_Design_Up_Front
Representational State Transfer (REST)
เป็นสถาปัตยกรรมที่ใช้สำหรับการแพร่กระจายสื่อ ที่ออกแบบมาให้มีความง่าย โดยการแลกเปลี่ยนข้อมูลกันระหว่าง HTTP และ XML โดยการร้องขอแบบ GET Request (การร้องขอโดยการส่ง attribute ของข้อมูลที่ต้องการแนบมากับ URL)ศึกษาข้อมูลเพิ่มเติม :
http://java-thai-talk.blogspot.com/2012/01/restful.html
https://eddyatlab.wordpress.com/2009/06/11/rest-representational-state-transfer/
Key
การแก้ไขเปลี่ยนแปลงข้อมูล หรือการกำหนดความสัมพันธ์ระหว่างข้อมูลจะเกิดขึ้นอย่างมีประสิทธิภาพ จะต้องกำหนด คีย์ (Key) ให้กับ Table ก่อน นอกจากนี้การกำหนดคีย์จะทำให้การอ้างอิงและการประมวลผลข้อมูลได้สะดวกขึ้นและยังช่วยประหยัดเนื้อที่ในการจัดเก็บศึกษาข้อมูลเพิ่มเติม :
http://www2.tsu.ac.th/cst/course/computer_it/database/key.html
สรุปการทดลองใช้งานคำสั่งตามหนังสือ TDD with Python
เริ่มต้นตามหนังสืออธิบายว่าตอนนี้ database ที่เราใช้ในการ Functional Test นั้นเป็น database จริง ซึ่งมีการบันทึกข้อมูลจากตัว test ลงไปใน db.sqlite3 ซึ่งจริงๆแล้วตัว test ไม่ควรจะไปยุ่งกับข้อมูลใน database ที่เราใช้งาน โดยใน Django มี Class ชื่อว่าLiveServerTestCase ที่จะช่วยจำลอง database ขึ้นมาให้เราซึ่ง
LiveServerTestCase ใช้ตัวรัน Functional Test ของ Django ซึ่งต้องรันที่ manage.py และจะค้นหาไฟล์ที่ขึ้นต้นด้วย test ซึ่งเราจะทำการสร้าง directory สำหรับ FT โดยเฉพาะ และเปลี่ยนชื่อ file เป็น test.pyสร้าง directory ใช้คำสั่ง
mkdir functional_tests
และต้องสร้าง init file สำหรับ directory ของ python ที่จะใช้กับ Django ใช้คำสั่ง
touch functional_tests/__init__.py
ย้ายไปยัง directory functional_testและเปลี่ยนชื่อเป็น tests.py ใช้คำสั่ง
git mv functional_tests.py functional_tests/tests.py
เปลี่ยน Class ใน FT ให้รับ parameter จาก
LiveServerTestCasefromdjango.testimportLiveServerTestCase
[....]
และเปลี่ยนจากการเรียก localhost port 8000 เป็นใช้ attribute ของclassNewVisitorTest(LiveServerTestCase):
LiveServerTestCaseคำสั่งในการรัน FT ก็จะเปลี่ยนไปสั่งที่ manage.pydeftest_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 homepageself.browser.get(self.live_server_url)
python3 manage.py test functional_tests
และคำสั่งที่จะรัน Unit Test ก็ต้องเปลี่ยนด้วยเนื่องจากมี FT เพิ่มเข้ามา จึงต้องเจาะจงไปที่ test ใน lists
python3 manage.py test lists
-------------------------------------------------------------------------------------------------------------------------------
ต่อไปเราจะใช้แนวคิดของ REST ซึ่งจะใช้ URL structure ในการทำงานในกรณีต่างๆ ของ app lists ของเรา
- โดยการระบุว่าจะเป็น list ไหน (รองรับหลาย list) ให้แต่ละ list มี URL เป็นของตัวเอง (ส่งการร้องขอไปยัง server แบบ GET Request คือการส่ง attribute ระบุข้อมูลที่ต้องการจาก server บนส่ง URL)
/lists/<คำที่จะเจาะจงว่าเป็น list ไหน (ชื่อ list)>/
- สร้าง list ใหม่ให้ส่งการร้องขอแบบ POST Request
/lists/new
- เพิ่มข้อมูลไปยัง list ที่มีอยู่แล้วทาง POST Request
/lists/<คำที่จะเจาะจงว่าเป็น list ไหน (ชื่อ list)>/add_item
จากนั้นเราจะเพิ่มตัว FT ให้ตรวจสอบการใช้งานหลายๆ list
จะตรวจสอบว่า URL นี้ตรงกับ regular expression (ลำดับของการเขียน URL) หรือไม่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 tableinputbox.send_keys(Keys.ENTER)edith_list_url=self.browser.current_urlself.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[...]
จากนั้นเราจะลอง test ให้สามารถใส่ข้อมูลได้หลาย list
เมื่อลองรัน FT แล้วจะ error ว่า[...]# The page updates again, and now shows both items on her listself.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# listself.browser.get(self.live_server_url)page_text=self.browser.find_element_by_tag_name('body').textself.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 URLfrancis_list_url=self.browser.current_urlself.assertRegex(francis_list_url,'/lists/.+')self.assertNotEqual(francis_list_url,edith_list_url)# Again, there is no trace of Edith's listpage_text=self.browser.find_element_by_tag_name('body').textself.assertNotIn('Buy peacock feathers',page_text)self.assertIn('Buy milk',page_text)# Satisfied, they both go back to sleep
-------------------------------------------------------------------------------------------------------------------------------AssertionError: Regex didn't match: '/lists/.+' not found in 'http://localhost:8081/'
ต่อไปหนังสือจะให้สร้างตัว Unit Test สำหรับการ Redirect เมื่อได้รับการร้องขอแบบ POST ให้ไปยัง
/lists/the-only-list-in-the-world/)เปลี่ยนจากกลับไปยัง home_page ('/')
และไปเปลี่ยนที่ views.py ด้วยself.assertEqual(response.status_code,302)self.assertEqual(response['location'],'/lists/the-only-list-in-the-world/')
ซึ่ง UT จะผ่านแต่ FT จะ error ว่าdefhome_page(request):ifrequest.method=='POST':Item.objects.create(text=request.POST['item_text'])returnredirect('/lists/the-only-list-in-the-world/')items=Item.objects.all()returnrender(request,'home.html',{'items':items})
self.check_for_row_in_list_table('1: Buy peacock feathers')
[...]
selenium.common.exceptions.NoSuchElementException: Message: 'Unable to locate
element: {"method":"id","selector":"id_list_table"}' ; Stacktrace:
เนื่องจากยังไม่มี list item ชื่อว่า the-only-list-in-the-world-------------------------------------------------------------------------------------------------------------------------------
ต่อไปเราจะใช้ Django test client ช่วย Unit Test ในการตรวจสอบ การ map URL ,การตรวจสอบ views และการตรวจสอบการ render template ของ views โดยการสร้าง Class ชื่อ
ListViewTestและย้ายฟังก์ชันtest_displays_all_items(self) มา
เมื่อลองรัน error เนื่องจากยังหา URL ไม่เจอว่าclassListViewTest(TestCase):deftest_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')#
เราจึงไปสร้าง URL ใหม่ที่ urls.pyAssertionError: 404 != 200 : Couldn't retrieve content: Response code was 404
รัน UT อีกครั้ง error ว่า import view_list ไม่ได้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)),)
AttributeError: 'module' object has no attribute 'view_list'
[...]
django.core.exceptions.ViewDoesNotExist: Could not import
lists.views.view_list. View does not exist in module lists.views.
ดังนั้นจึงไปสร้าง view_list ใน views.pyเมื่อรัน UT จะผ่านแต่รัย FT จะ error ว่าdefview_list(request):items=Item.objects.all()returnrender(request,'home.html',{'items':items})
เนื่องจากเราใช้ template เดียวกันทั้งหน้า home page และ หน้าที่จะแสดง list (home.html)AssertionError: '2: Use peacock feathers to make a fly' not found in ['1: Buy peacock feathers']
ต่อไปจะทำการ refactoring เอา test ที่ไม่จำเป็นแล้วออกจาก unit test
ดูว่าในไฟล์นั้นมี class และ function อะไรบ้างใช้คำสั่ง
grep -E "class|def" lists/tests.py
เราจะลบ
test_home_page_displays_all_list_items ออกเนื่องจากไม่จำเป็นแล้ว-------------------------------------------------------------------------------------------------------------------------------
ต่อไปเราจะทำการแบ่ง template ของ home page กับ แสดง list ออกจากกัน
เริ่มต้นด้วยการสร้าง Unit Test ตรวจสอบว่าหน้าแสดง list ก็ใช้ template ของมันเอง
และไปเปลี่ยนที่ views.py ให้เรียก list.htmlclassListViewTest(TestCase):deftest_uses_list_template(self):response=self.client.get('/lists/the-only-list-in-the-world/')self.assertTemplateUsed(response,'list.html')deftest_displays_all_items(self):[...]
เมื่อสร้าง test เสร็จจึงไปสร้าง template มา สร้างไฟล์ใหม่ใช้คำสั่งdefview_list(request):items=Item.objects.all()returnrender(request,'list.html',{'items':items})
touch lists/templates/list.html
แล้วก็ copy เนื้อใน file มาจาก home.html ก่อน
cp lists/templates/home.html lists/templates/list.html
จากนั้นที่หน้า home เราจะเปลี่ยนคำที่แสดงบนหน้าแรกว่า start a new to-do list ให้สื่อความหมายว่าเป็นหน้าเริ่มต้น
และไปแก้ที่ views.py ให้หลังจาก POST Request แล้วให้ redirect ไปหน้า list<body><h1>Start a new To-Do list</h1><formmethod="POST"><inputname="item_text"id="id_new_item"placeholder="Enter a to-do item"/>{% csrf_token %}</form></body>
เมื่อรัน UT จะผ่าน แต่ FT จะ errordefhome_page(request):ifrequest.method=='POST':Item.objects.create(text=request.POST['item_text'])returnredirect('/lists/the-only-list-in-the-world/')returnrender(request,'home.html')
จากนั้นแก้ที่ list.html ให้มี action ไปยัง home_page (ไม่ให้กลับมาที่เดิมตาม default ของมัน)
เมื่อลองรัน FT จะ error ว่า<formmethod="POST"action="/">
แต่เราก็ได้ทำการ refactoring การเรียกใช้ template แยกกันเสร็จแล้วself.assertNotEqual(francis_list_url, edith_list_url) AssertionError: 'http://localhost:8081/lists/the-only-list-in-the-world/' == 'http://localhost:8081/lists/the-only-list-in-the-world/'
-------------------------------------------------------------------------------------------------------------------------------
ตอนนี้เรามี URL สำหรับแต่ละ list แต่ยังไม่มีการสร้าง list หลายๆ list ไว้เก็บ
ดังนั้นเริ่มต้นด้วยการสร้าง UT ใหม่ แต่ย้าย 2 ฟังก์ชั่นนี้มา
แล้วเราก็จะแก้ไขให้ใช้การทำงานของ Django test clientclassNewListTest(TestCase):deftest_saving_a_POST_request(self):request=HttpRequest()request.method='POST'[...]deftest_redirects_after_POST(self):[...]
สังเกตว่า URL ที่ไม่มี '/' ปิดท้ายจะหมายถึง URL ที่จะดำเนินการแก้ไขข้อมูลใน databaseclassNewListTest(TestCase):deftest_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')deftest_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/')
ลองรัน UT จะ error ว่า
self.assertEqual(Item.objects.count(), 1)
AssertionError: 0 != 1
[...]
self.assertEqual(response.status_code, 302)
AssertionError: 404 != 302
เพราะเรายังไม่มี URL ชื่อว่า list/new ดังนั้นไปเพิ่มที่ urls.pyและเพิ่มที่ views.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)),)
UT จะ error ว่า "The view lists.views.new_list didn’t return an HttpResponse object" ดังนั้นต้องให้มัน returndefnew_list(request):pass
เมื่อรันจะกลับไป errordefnew_list(request):returnredirect('/lists/the-only-list-in-the-world/')
เราจึงไปเพิ่มฟังก์ชันใน views.pyself.assertEqual(Item.objects.count(), 1) AssertionError: 0 != 1 [...] AssertionError: 'http://testserver/lists/the-only-list-in-the-world/' != '/lists/the-only-list-in-the-world/'
และไปแก้การ redirect ใน list.py ให้ใช้ความสามารถของ Django test clientdefnew_list(request):Item.objects.create(text=request.POST['item_text'])returnredirect('/lists/the-only-list-in-the-world/')
เมื่อรัน UT ก็จะผ่านdeftest_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/')
-------------------------------------------------------------------------------------------------------------------------------
ต่อไปก็จะไป refactoring ใน views.py ด้วยการลบส่วน
if request.method == 'POST' ออกdefhome_page(request):returnrender(request,'home.html')
ต่อไปเราจะแก้ไขให้ template ทั้งสองอันเชื่อมต่อกันด้วย list/new URL โดยไปแก้ทั้งสอง template เป็น
-------------------------------------------------------------------------------------------------------------------------------<formmethod="POST"action="/lists/new">
ต่อไปจะเป็นการปรับ model ของเราให้มีโมเดลที่รวบรวม list ด้วย โดยเริ่มแก้ไขที่ UT
@@ -3,7 +3,7 @@ from django.http import HttpRequestfrom django.template.loader import render_to_string from django.test import TestCase-from lists.models import Item+from lists.models import Item, Listfrom lists.views import home_page class HomePageTest(TestCase):@@ -60,22 +60,32 @@ class ListViewTest(TestCase):-class ItemModelTest(TestCase):+class ListAndItemModelsTest(TestCase):def test_saving_and_retrieving_items(self):+ list_ = List()+ list_.save()+first_item = Item() first_item.text = 'The first (ever) list item'+ first_item.list = list_first_item.save() second_item = Item() second_item.text = 'Item the second'+ second_item.list = list_second_item.save()+ saved_list = List.objects.first()+ self.assertEqual(saved_list, list_)+saved_items = Item.objects.all() self.assertEqual(saved_items.count(), 2) first_saved_item = saved_items[0] second_saved_item = saved_items[1] self.assertEqual(first_saved_item.text, 'The first (ever) list item')+ self.assertEqual(first_saved_item.list, list_)self.assertEqual(second_saved_item.text, 'Item the second')+ self.assertEqual(second_saved_item.list, list_)
เมื่อรัน UT ก็แน่นอนว่าจะไม่พบ List ดังนั้นจึงไปเพิ่มที่ models.py ให้มี Class ของ List
จะ error ว่าclassList(models.Model):pass
ดังนั้นเราก็จะไปสร้าง table ของ list ขึ้นมา ด้วย migrations เป็น table ของ listdjango.db.utils.OperationalError: no such table: lists_list
python3 manage.py makemigrations
และเมื่อลองรันอีกครั้งจะ error ว่าเนื่องจาก table ของ Item และ list นั้นยังไม่มีความสัมพันธ์ซึ่งกันและกัน ดังนั้นเราจะเพิ่มให้มีself.assertEqual(first_saved_item.list, list_) AttributeError: 'Item' object has no attribute 'list'
Foreign Key ซึ่งจะเชื่อม table ให้มีความสัมพันธ์กัน โดยการแก้ดังนี้
และสร้าง migrations ใหม่สำหรับ list โดยจะ add field list เข้าไปใน Itemfromdjango.dbimportmodelsclassList(models.Model):passclassItem(models.Model):text=models.TextField(default='')list=models.TextField(default='')
python3 manage.py makemigrations
แต่ UT error ว่า ซึ่งต้องบอก Django ว่า 2 Class นี้สัมพันธ์กันAssertionError: 'List object' != <List: List object>
ลบ migrations แล้วสร้างใหม่fromdjango.dbimportmodelsclassList(models.Model):passclassItem(models.Model):text=models.TextField(default='')list=models.ForeignKey(List,default=None)
แต่ UT จะ error ว่าrm lists/migrations/0004_item_list.py python3 manage.py makemigrations
โดยจะต้องมีการสร้าง list ให้ตอนที่ test การใส่ item ด้วยERROR: test_displays_all_items (lists.tests.ListViewTest) django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id [...] ERROR: test_redirects_after_POST (lists.tests.NewListTest) django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id [...] ERROR: test_saving_a_POST_request (lists.tests.NewListTest) django.db.utils.IntegrityError: NOT NULL constraint failed: lists_item.list_id Ran 7 tests in 0.021s FAILED (errors=3)
และแก้ใน views.pyclassListViewTest(TestCase):deftest_displays_all_items(self):list_=List.objects.create()Item.objects.create(text='itemey 1',list=list_)Item.objects.create(text='itemey 2',list=list_)
เมื่อรัน Unit Test ก็จะผ่านfromlists.modelsimportItem,List[...]defnew_list(request):list_=List.objects.create()Item.objects.create(text=request.POST['item_text'],list=list_)returnredirect('/lists/the-only-list-in-the-world/')
-------------------------------------------------------------------------------------------------------------------------------
ต่อไปเราจะทำให้แต่ละ list มี URL เป็นของตัวเองโดยเป็นชื่อของ list นั่นเอง โดยแก้ที่ unit test
และแก้ที่ urls.pyclassListViewTest(TestCase):deftest_uses_list_template(self):list_=List.objects.create()response=self.client.get('/lists/%d/'%(list_.id,))self.assertTemplateUsed(response,'list.html')deftest_displays_only_items_for_that_list(self):correct_list=List.objects.create()Item.objects.create(text='itemey 1',list=correct_list)Item.objects.create(text='itemey 2',list=correct_list)other_list=List.objects.create()Item.objects.create(text='other list item 1',list=other_list)Item.objects.create(text='other list item 2',list=other_list)response=self.client.get('/lists/%d/'%(correct_list.id,))self.assertContains(response,'itemey 1')self.assertContains(response,'itemey 2')self.assertNotContains(response,'other list item 1')self.assertNotContains(response,'other list item 2')
โดยในตำแหน่งของ (.+) จะใส่เป็นชื่อของ list ที่จะรับมาจากการ parameter ของ views.pyurlpatterns=patterns('',url(r'^$','lists.views.home_page',name='home'),url(r'^lists/(.+)/$','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)),)
และไปแก้ที่ UT ในการตรวจสอบการ redirect ว่าdefview_list(request,list_id):list_=List.objects.get(id=list_id)items=Item.objects.filter(list=list_)returnrender(request,'list.html',{'items':items})
เมื่อรัน Unit Test จะผ่าน แต่ Functional Test จะ error ว่าdeftest_redirects_after_POST(self):response=self.client.post('/lists/new',data={'item_text':'A new list item'})new_list=List.objects.first()self.assertRedirects(response,'/lists/%d/'%(new_list.id,))
-------------------------------------------------------------------------------------------------------------------------------AssertionError: '2: Use peacock feathers to make a fly' not found in ['1: Use peacock feathers to make a fly']
ต่อไปจะเป็นการเพิ่ม item ที่รับมาไปยัง list ที่ต้องการ โดยเริ่มจาก UT
จะ errorclassNewItemTest(TestCase):deftest_can_save_a_POST_request_to_an_existing_list(self):other_list=List.objects.create()correct_list=List.objects.create()self.client.post('/lists/%d/add_item'%(correct_list.id,),data={'item_text':'A new item for an existing list'})self.assertEqual(Item.objects.count(),1)new_item=Item.objects.first()self.assertEqual(new_item.text,'A new item for an existing list')self.assertEqual(new_item.list,correct_list)deftest_redirects_to_list_view(self):other_list=List.objects.create()correct_list=List.objects.create()response=self.client.post('/lists/%d/add_item'%(correct_list.id,),data={'item_text':'A new item for an existing list'})self.assertRedirects(response,'/lists/%d/'%(correct_list.id,))
AssertionError: 301 != 302 : Response didn't redirect as expected: Response
code was 301 (expected 302)
เราจะแก้ที่ urls.py โดยใช้ regular expression \dก็จะเปลี่ยนมาเป็นurl(r'^lists/(\d+)/$','lists.views.view_list',name='view_list'),
AssertionError: 404 != 302 : Response didn't redirect as expected: Response
code was 404 (expected 302)
เนื่องจากไม่พบหน้า add_item โดยจะเพิ่มและจะต้องไปเพิ่ม add ที่ views ด้วยurlpatterns=patterns('',url(r'^$','lists.views.home_page',name='home'),url(r'^lists/(\d+)/$','lists.views.view_list',name='view_list'),url(r'^lists/(\d+)/add_item$','lists.views.add_item',name='add_item'),url(r'^lists/new$','lists.views.new_list',name='new_list'),# url(r'^admin/', include(admin.site.urls)),)
defadd_item(request,list_id):list_=List.objects.get(id=list_id)Item.objects.create(text=request.POST['item_text'],list=list_)returnredirect('/lists/%d/'%(list_.id,))
และในหน้า template เราอยากให้มีการ action ที่ add_item ก็แก้ที่ form ว่า
และสร้าง UT ใหม่เพื่อทดสอบว่า list ที่ list template นั้นถูกอัน<formmethod="POST"action="/lists/{{ list.id }}/add_item">
และแก้ไขที่ views.pydeftest_passes_correct_list_to_template(self):other_list=List.objects.create()correct_list=List.objects.create()response=self.client.get('/lists/%d/'%(correct_list.id,))self.assertEqual(response.context['list'],correct_list)
จะ error ว่าdefview_list(request,list_id):list_=List.objects.get(id=list_id)returnrender(request,'list.html',{'list':list_})
AssertionError: False is not true : Couldn't find 'itemey 1' in response
โดยจะแก้ที่ list template ให้เอาข้อมูลมาจาก list
เมื่อรัน UT และ FT จะผ่าน<formmethod="POST"action="/lists/{{ list.id }}/add_item">[...] {% for item in list.item_set.all %}<tr><td>{{ forloop.counter }}: {{ item.text }}</td></tr>{% endfor %}
-------------------------------------------------------------------------------------------------------------------------------
สุดท้ายจะเป็นการแยก URL ที่เกี่ยวข้องกับหน้า home_page และหน้า list ออกจากกัน
โดยจะย้ายของ list ซึ่งเป็นของ app ไปไว้ใน app lists
cp superlists/urls.py lists/
urls.py ของ home_page ใน superlists
urls.py ของ list ใน listsurlpatterns=patterns('',url(r'^$','lists.views.home_page',name='home'),url(r'^lists/',include('lists.urls')),# url(r'^admin/', include(admin.site.urls)),)
fromdjango.conf.urlsimportpatterns,urlurlpatterns=patterns('',url(r'^(\d+)/$','lists.views.view_list',name='view_list'),url(r'^(\d+)/add_item$','lists.views.add_item',name='add_item'),url(r'^new$','lists.views.new_list',name='new_list'),)
ศึกษาข้อมูลเพิ่มเติม :
http://chimera.labs.oreilly.com/books/1234000000754/ch06.html#_a_final_refactor_using_url_includes














