วันศุกร์ที่ 15 พฤษภาคม พ.ศ. 2558
วันพุธที่ 6 พฤษภาคม พ.ศ. 2558
วันจันทร์ที่ 6 เมษายน พ.ศ. 2558
Test 2
BitBucket https://bitbucket.org/pongsarkorn_chinbut/test-2/src
Sort : by time
Sort : min to max
Sort : max to min
Sort : by time
Sort : min to max
Sort : max to min
วันอาทิตย์ที่ 29 มีนาคม พ.ศ. 2558
A1 - Electricity Charge Calaulator Web Application
BitBucket Link
https://bitbucket.org/pongsarkorn_chinbut/a1/commits/all
Unit Test ยังมีปัญหาในฟังก์ชันตรวจสอบการคำนวนค่าเนื่องจากยังไม่สามารถเรียกดูโค้ด html จาก response ได้
https://bitbucket.org/pongsarkorn_chinbut/a1/commits/all
Unit Test ยังมีปัญหาในฟังก์ชันตรวจสอบการคำนวนค่าเนื่องจากยังไม่สามารถเรียกดูโค้ด html จาก response ได้
Functional Test มีการตรวจสอบการใช้งานสำหรับผู้ใช้งานหลายคน ผ่านแล้ว
แต่ในการรันด้วย Server ที่ 0.0.0.0:8000 ยังมีปัญหาอยู่เช่นเดิม (http://bss56-20056.blogspot.com/2015/03/a1_23.html)
วันจันทร์ที่ 23 มีนาคม พ.ศ. 2558
สิ่งที่ได้ไปทำเพิ่มเติมใน A1 (เพิ่มเติม)
ตอนนี้สามารถทำให้ Functional Test มีการแยก URL ในการใส่ข้อมูลเครื่องใช้ไฟฟ้าของแต่ละคนได้แล้ว
เมื่อใส่ข้อมูลของคนแรกแล้วกด submit จะ redirect จาก root มาที่ /electricity_charge/1/
เมื่อใส่ข้อมูลของคนที่สองแล้วกด submit จะ redirect จาก root มาที่ /electricity_charge/2/
แต่เมื่อรันด้วย server ที่ 0.0.0.0:8000 เมื่อกดส่งข้อมูลยังขึ้นหน้านี้อยู่
เมื่อใส่ข้อมูลของคนแรกแล้วกด submit จะ redirect จาก root มาที่ /electricity_charge/1/
เมื่อใส่ข้อมูลของคนที่สองแล้วกด submit จะ redirect จาก root มาที่ /electricity_charge/2/
แต่เมื่อรันด้วย server ที่ 0.0.0.0:8000 เมื่อกดส่งข้อมูลยังขึ้นหน้านี้อยู่
วันอาทิตย์ที่ 22 มีนาคม พ.ศ. 2558
สิ่งที่ได้ไปทำเพิ่มเติมใน A1
ตอนนี้ทำถึงให้แต่ละหน้าของการคำนวนค่าไฟฟ้ามี URL เป็นของตนเองแล้ว Unit test ผ่านทั้งหมด แต่เกิดปัญหาตรงที่ Functional test ขึ้นว่า Server error 500
ขึ้น error ดังนี้
เมื่อลองรันโดย server ไปที่ 0.0.0.0:8000 เมื่อลองกดส่งข้อมูลขึ้นดังนี้
ขึ้น error ดังนี้
เมื่อลองรันโดย server ไปที่ 0.0.0.0:8000 เมื่อลองกดส่งข้อมูลขึ้นดังนี้
วันพฤหัสบดีที่ 12 กุมภาพันธ์ พ.ศ. 2558
Test-Driven Development with Python - สรุป Chapter. 6
คำสำคัญที่พูดถึงในหนังสือ 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
วันพฤหัสบดีที่ 5 กุมภาพันธ์ พ.ศ. 2558
GIT - การนำกลับข้อมูลที่แก้ไขแล้วในรูปแบบต่างๆ
Data Recovery
รูปแบบนี้จะเป็นการเลือกย้อนไปยังการ commit ที่เราต้องการและล้างการแก้ไขก่อนหน้าการ commit ครั้งนั้นทั้งหมดคำสั่งเรียกดูประวัติการ commit ของ git
เมื่อใช้คำสั่งแล้วจะแสดงประวัติการ commit โดยจะแสดงตัวเลขฐาน 16 และ comment ที่เรา commit ไว้git log --pretty=oneline
คำสั่งกู้คืนการแก้ไขข้อมูลที่เคย commit ไว้ เป็นการย้อนสถาณะของไฟล์ทั้งหมดกลับไปเหมือนตอนที่ commit ครั้งนั้นไว้
git reset --hard 8436f9b2b51283f5088f2de488b58282c7a5e432
โดยใส่ตัวเลขด้านหน้า commit ที่เราต้องการกู้คืนเมื่อใช้คำสั่งแล้วจะเห็นว่า commit ล่าสุดจะเป็น commit ที่เราต้องการ
ศึกษาเพิ่มเติม :
http://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery#_data_recovery
Unmodifying a Modified File
รูปแบบนี้จะเป็นการล้างการแก้ไขก่อนหน้าการ commit ครั้งล่าสุด
ลองทำการแก้ไขไฟล์ที่ได้ commit ไปแล้ว (functional_tests.py) ก่อนหน้าการ commit จะเป็นลักษณะดังภาพ
ลองทำการแก้ไขไฟล์ที่ได้ commit ไปแล้ว (functional_tests.py) ก่อนหน้าการ commit จะเป็นลักษณะดังภาพ
ทดลองใส่ comment code ส่วนฟังก์ชััน setUp และ tearDown (ทำการแก้ไขไฟล์)
เช็ค git status จะเห็นว่าไฟล์ functional_tests.py ถูกแก้ไข และบอกว่าการแก้ไขนั้นยังไม่ได้ถูก commit ไปยัง git
หากต้องการให้เนื้อหาของไฟล์กลับไปเป็นเหมือนก่อนหน้าที่ commit ใช้คำสั่ง
git checkout -- ชื่อไฟล์เมื่อลองใช้คำสั่งและลองเช็ค git status จะเห็นว่าไม่มีการแก้ไขไฟล์ functional_tests.py แล้ว
เมื่อลองดูไฟล์ functional_tests.py จะเห็นว่าไม่มีการ comment code เหมือนกับก่อนที่จะทำการ commit
ศึกษาเพิ่มเติม :
http://git-scm.com/book/en/v2/Git-Basics-Undoing-Things
Branches
รูปแบบนี้จะเป็นการสร้าง "กิ่ง" แยกออกไปเพื่อแก้ไขเพิ่มเติม และเมื่ออยากกลับมาเริ่มแก้ไขที่ commit เก่าๆก็สามารถสร้างกิ่งใหม่ออกไปอีกทาง โดยจะไม่มีผลกระทบต่อกัน และไม่มีผลกระทบต่อ commit เดิมด้วยเริ่มต้นด้วยการดูการ commit ครั้งล่าสุดด้วยคำสั่ง
git log --oneline --decorate
โดยคำสั่ง --decorate จะแสดงกิ่งและตัวชี้(pointer)ว่าขณะนี้อยู่ที่ commit ไหน โดยกิ่งที่ git จะสร้างขึ้นมาตั้งแต่เริ่มต้นจะชื่อว่า master ซึ่งจะอยู่ที่ commit ครั้งล่าสุดของเรา และมีตัวชี้ชื่อว่า HEAD
จากนั้นเราจะสร้างกิ่งใหม่ชื่อว่า testing โดยใช้คำสั่ง
git branch testing
จะได้กิ่งใหม่ขึ้นมาอยู่ตำแหน่งเดียวกับกิ่งที่เราทำงานด้วยอยู่ปัจจุบันคือ master
จากนั้นเราจะย้ายตัว pointer ไปยังกิ่ง testing เพื่อเริ่มทำงานที่กิ่ง testing ใช้คำสั่ง
git checkout testing
ทดลองทำการสร้างไฟล์ชื่อว่า new_file.py
ทำการ add ไฟล์นี้ไปยัง git และทำการ commit การแก้ไขนี้ว่า 'made a change'
เมื่อลองใช้คำสั่ง git log --oneline --decorate อีกครั้งจะเห็นว่ามีการ commit เพิ่มขึ้นมา
สังเกตว่าตัวกิ่ง master ไม่ได้ตามมาด้วย
เมื่อลองกลับไปให้ HEAD ชี้ที่ master ใช้คำสั่ง
git checkout master
จะเห็นว่าไฟล์ new_file.py หายไปเมื่อลองกลับมาที่ testing ไฟล์ new_file.py ก็จะกลับมา
ศึกษาเพิ่มเติม :
http://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell
สมัครสมาชิก:
บทความ (Atom)



























