วันพุธที่ 24 กุมภาพันธ์ พ.ศ. 2559

Assignment 1 : Word Bank (Part 1)

ออกแบบหน้าเว็บไซต์ที่เราต้องการจะให้เป็น

หน้าแรกที่เข้าเว็บไซต์มา


เมื่อกด Search



Start Project ชื่อ WordBankWebsite

สร้าง git repository

และสร้าง app ชื่อ WordBank

จากนั้นสร้างไฟล์สำหรับ functional test


จะเริ่มจากการ functional test GUI ของ หน้า homepage ให้ได้แบบที่ต้องการ

User story



เริ่มเขียน functional test

เริ่มจากการทดสอบ title ว่ามีคำว่า Word Bank Website ไหม


พอลองรันเทส



แน่นอนว่าต้อง Error !!

มาทำให้เทสผ่านกันเถอะ

มาวิเคราะห์ดูแล้ว ที่ยังรันเทสไม่ผ่าน เพราะยังไม่มีหน้าแรกเลยด้วยซ้ำ

แสดงว่าเราต้องสร้าง index view

เขียน unit test เพื่อทำการเทสว่าเรามี index view แล้วหรือยัง


ลองเทส


ไม่พบ index

เพิ่ม index view ใน /WordBankWebsite/WordBank/views.py



จากตรง RegexURLPattern ดูเหมือนกับว่า มีปัญหาที่ URL

เรายังไม่ได้ URL Config เลย

สร้าง /WordBankWebsite/WordBank/urls.py


และแก้ไขไฟล์ /WordBankWebsite/WordBankWebsite/urls.py


ลองรันเทส


เทสผ่าน

ลองรัน functional test



เหตุผลที่ Unit Test ผ่าน เพราะ resolve ไปที่ '/' ได้ แต่ Functional Test ไม่ผ่าน เพราะถึงจะ resolve ไปได้ แต่เราก็ยังไม่มีหน้าเพจตามที่เราต้องการอยู่ดี

เขียน Unit Test เพื่อเทสว่า view return หน้าเพจ html ที่เราต้องการ




ทำการเขียนโค้ดเพื่อให้เทสผ่าน


ลองรันเทส


รัน Unit Test ผ่านแล้ว ลองรัน Functional Test




Functional Test ก็ผ่าน เย้ !!

แต่ว่า ด้วยความรู้ Django พื้นฐานที่มี เราสามารถใช้ Template ได้

จะทำการ Refactor index view

เซ็ต WordBank app ให้อยู่ใน INSTALLED_APPS

เปิด WordBankWebsite/WordBankWebsite/settings.py

เพิ่ม 'WordBank.apps.WordbankConfig', เข้าไป


สร้าง Directory /WordBankWebsite/WordBank/templates/WordBank/

และสร้างไฟล์ index.html ไว้ข้างใน


แต่เมื่อลองรันเทส


Unit Test เทสไม่ผ่าน แต่ Functional Test เทสผ่าน !!!

เป็นเพราะ response ที่ได้กลับมา ไม่ได้อยู่ในรูปของ text

ต้องแก้ไขเทส


 และแล้ว ก็รันเทสผ่าน


ในเมื่อเทสผ่านแล้ว รีแฟคเตอร์แล้ว ก็ทำการเขียน Functional test ต่อ และทำตาม TDD Cycle ต่อไป !!

วันอาทิตย์ที่ 14 กุมภาพันธ์ พ.ศ. 2559

Test-Driven Development with Python Chapter 4

โปรแกรมเมอร์เหมือนกับตักน้ำขึ้นมาจากบ่อ

ในการตักน้ำขึ้นมา แรกๆอาจจะยังง่ายๆอยู่ แต่เมื่อตักไปเรื่อยๆ คุณอาจจะเหนื่อย ต้องการการพัก คุณก็จะใช้วงล้อมาล็อคไว้ และทำให้คุณได้พัก

#TDDก็เช่นกัน

เพราะการ Programming แรกๆอาจจะยังง่ายๆอยู่ แต่เมื่อคุณทำไปเรื่อยๆ คุณต้องการการพัก TDD จะช่วยรักษาขั้นตอนและกระบวนการของคุณไว้ เพราะคุณมีเทสไว้ เมื่อกลับมาพัฒนาต่อ ก็จะไม่หลุดประเด็นจากที่ตั้งไว้


ในบางครั้งคุณอาจจะคิดว่า "ทำเกินไปหรือเปล่า ?" ที่มานั่งเทสสิ่งเล็กๆ ไปทีละสเต็ปเล็กๆ

แน่นอนแหละที่คุณคิดแบบนั้น มันเป็นธรรมชาติ แต่ TDD เป็นเหมือนระเบียบวินัย ที่คุณต้องบังคับตัวเองให้ปฏิบัติตาม เพราะมันจะส่งต่อคุณในระยะยาว


วันพุธที่ 10 กุมภาพันธ์ พ.ศ. 2559

Test-Driven Development with Python Chapter 3

ทดสอบ Homepage อย่างง่ายด้วย Unit Tests !!

จาก chapter ที่แล้ว ที่เรารัน functional test ไม่ผ่านเพราะเราต้องการให้มีคำว่า To-Do ใน title ของ homepage
คราวนี้แหละ จะถึงเวลาที่เราจะเริ่มทำแอพลิเคชั่น

เริ่มทำ Django App และทำ Unit Tests !!

สร้าง app ชื่อ lists สำหรับทำ To-Do lists

$ python3 manage.py startapp lists




ความแตกต่างระหว่าง Functional Tests และ Unit Tests

Functional Test เป็นการเทสจากภายนอก ในมุมมองของผู้ใช้ ส่วน Unit Test จะเป็นการเทสจากภายใน ในมุมมองของ Programmer

TDD ประกอบด้วยเทสทั้งสองแบบ โดยจะมีลำดับการทำงานดังนี้
1.เราจะเริ่มจากการเขียน Functional Test เพื่อระบุการทำงานของแอพพลิชั่น จากมุมมองของผู้ใช้
2.หลังจากเราเทส Functional Tests ไม่ผ่าน เราก็จะคิดถึงวิธีการที่จะเขียนโค้ดเพื่อให้เทสผ่าน โดยเราจะใช้ Unit test อย่างน้อย 1 Unit Tests เพื่อกำหนดว่าโค้ดของเราควรจะเป็นยังไง โดยแนวคิดคือ โค้ดแต่ละบรรทัดควรจะถูกเทสโดย Unit tests อย่างน้อย 1 Unit test
3.หลังจากเราเทส Unit Tests ไม่ผ่าน ให้เขียนโค้ดให้น้อยที่สุดเพื่อให้เทสผ่าน เราอาจจะทำ step 2 และ 3 หลายๆครั้ง จนกว่าเราคิดว่า Functional Test น่าจะเทสผ่านเยอะขึ้น
4.ลองเทส Functional Tests อีกรอบ และดูว่าเทสผ่านหรือยัง ถ้ายังไม่ผ่านให้เขียน Unit Tests และเขียนโค้ดเพิ่ม

Unit Tests ใน Django

เปิดไฟล์ tests.py ใน /superlists/lists

จะเห็นได้ว่า Django แนะนำให้เราใช้ TestCase ซึ่งเป็น unittest พื้นฐานที่ Django เพิ่มเติมบางส่วนมาเพื่อใช้งานกับ Django โดยเฉพาะ
สำหรับ Functional Tests เรารันด้วยตัวเองโดยตรง แต่ Unit test ที่เรากำลังเขียนจะรันโดยอัตโนมัติโดย Test runner

แก้ไขไฟล์ tests.py เป็นดังนี้

from django.test import TestCase

class SmokeTest(TestCase):
  def test_bad_maths(self):
    self.assertEqual(1 + 1, 3)

รันจากนั้นให้เรารันเทส โดย $ python3 manage.py test

จะเกิด Error ขึ้นมา

 

ดูเหมือนว่า test จะใช้งานได้

ให้ commit เก็บไว้

$ git add lists
$ git commit -m"Add app for lists, with deliberately failing unit test"

ต่อมา เราตั้งเป้าจะเทส 2 อย่าง
1.เราสามารถ resolve URL ที่ / ไปหา view ที่เราสร้างได้ไหม
2.เราสามารถทำให้ view return HTML ที่ทำให้ Functional Test ผ่านได้

เริ่มจากอย่างแรก แก้ไขไฟล์ tests.py

from django.core.urlresolvers import resolve
from django.test import TestCase
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)

เมื่อเราลองรันเทส เราจะรันไม่ผ่าน


ให้เราเขียนโค้ดเพื่อให้เทสผ่าน

แก้ไฟล์ views.py ใน /superlists/lists

from django.shortcuts import render

# Create your views here.
home_page = None

เมื่อรันเทส


จากการเทสด้านบน ทำให้เรารู้ว่า Django หา URL mapping ของ / ไม่เจอ

แก้ไฟล์ urls.py ใน /superlists/superlists

urlpatterns = patterns('',
  url(r'^$', 'lists.views.home_page', name='home'),
)

และแก้ views.py ใน /superlists/lists

from django.shortcuts import render

# Create your views here.
def home_page():
  pass

เมื่อลองรันเทสดู


เทสผ่าน เย้ !!

commit เก็บไว้

ต่อมาเราเทสอย่างที่สองกัน

ใส่โค้ด from django.http import HttpRequest เข้าไปใน Unit test

เพิ่ม Method ที่ไว้เทส Unit Test นี้เข้าไป

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>')) #

รันเทส


แก้ไข views.py เพื่อให้เทสผ่าน ดูว่าเทสไม่ผ่านเพราะอะไร และเพิ่มโค้ดไปเรื่อยๆ จนสุดท้ายใน views.py จะมีหน้าตาแบบนี้

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.
def home_page(request):
    return HttpResponse('<html><title>To-Do lists</title></html>')

แล้วเราก็จะเทสผ่าน

หลังจากนั้น ลองรัน Functional Test

ก็จะพบว่าเทสผ่าน !!

commit เก็บไว้ !!

Test-Driven Development with Python Chapter 2

ใช้ Functional Test เพื่อระบุสิ่งที่แอปของเราอย่างน้อยต้องทำได้

Functional Test คือ การทดสอบโดยมุมมองของ user เกี่ยวกับการทำงานของแอปเรา โดยที่ไม่สนใจระบบภายใน ซึ่งเราต้องระบุว่า user น่าจะต้องทำอะไรกับแอปของเรา และแอปจะต้องตอบสนองยังไง

แก้ไขไฟล์ functional_test.py ตามนี้

from selenium import webdriver
browser = webdriver.Firefox()
# Edith has heard about a cool new online to-do app. She goes
# to check out its homepage
browser.get('http://localhost:8000')

# She notices the page title and header mention to-do lists
assert 'To-Do' in browser.title

# She is invited to enter a to-do item straight away

# She types "Buy peacock feathers" into a text box (Edith's hobby
# is tying fly-fishing lures)

# When she hits enter, the page updates, and now the page lists
# "1: Buy peacock feathers" as an item in a to-do list

# There is still a text box inviting her to add another item. She
# enters "Use peacock feathers to make a fly" (Edith is very methodical)

# The page updates again, and now shows both items on her list

# Edith wonders whether the site will remember her list. Then she sees
# that the site has generated a unique URL for her -- there is some
# explanatory text to that effect.

# She visits that URL - her to-do list is still there.

# Satisfied, she goes back to sleep
browser.quit()


จากนั้น run server และรัน functional_test.py


จะขึ้น AssertionError มา ซึ่งเป็นสิ่งที่เราคาดไว้ เพราะเรายังไม่ได้เขียนโค้ดเพื่อทำให้เทสผ่าน ถ้าไม่ Error สิแปลก

Library ที่ช่วยในการ test ที่ชื่อว่า unittest

from selenium import webdriver
import unittest


class NewVisitorTest(unittest.TestCase): #


  def setUp(self): #
    self.browser = webdriver.Firefox()
  def tearDown(self): #
    self.browser.quit()
  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('http://localhost:8000')
    # She notices the page title and header mention to-do lists
    self.assertIn('To-Do', self.browser.title) #
    self.fail('Finish the test!') #
    # She is invited to enter a to-do item straight away


if __name__ == '__main__': #
    unittest.main(warnings='ignore')


จากนั้นลองรันเทส


และก็อย่าลืม commit

TDD Concept ที่เป็นประโยชน์
- User Story คือการอธิบายว่าแอพลิเคชั่นจะทำงานยังไงในมุมมองของ user ซึ่งใช้เพื่อสร้าง functional test
- Expected Failure คือการเทสไม่ผ่านในทางที่เราคาดหวังจะให้มันเกิด

วันอาทิตย์ที่ 7 กุมภาพันธ์ พ.ศ. 2559

Test-Driven Development with Python Chapter 1 (Continued)

ต่อจากคราวที่แล้ว Chapter 1 เรายังไม่จบ

ลองใช้ git (เป็น Version Control System ตัวหนึ่ง)

ย้ายไฟล์ functional_test.py เข้าไปใน /superlists





cd เข้าไปใน /superlists และสร้าง git repository

$ git init .

จากนั้น เราจะทำการ commit

แต่เราไม่ต้องการให้ commit database ไปด้วย จึงใช้คำสั่ง

$ echo "db.sqlite3" >> .gitignore

และ add file ทั้งหมดใน /superlists ลง git repository

$ git add .

และเช็คว่าทำการ add file อะไรไปบ้าง

$ git status


จากนั้น เขาให้ลบไฟล์ .pyc เพราะมันไม่มีประโยชน์ที่จะ commit และ ทำการ ignore

$ git rm -r --cached superlists/__pycache__
$ echo "__pycache__" >> .gitignore
$ echo "*.pyc" >> .gitignore


และทำการ commit

** ก่อน commit ให้ $ git config --global user.email "you@example.com" และ $ git config --global user.name "Your Name" เพื่อเซ็ต email และชื่อ **




Test-Driven Development with Python Chapter 1

Install pip3

$ sudo apt-get install python3-pip

Install Selenium

$ sudo pip3 install selenium

Install git

$ sudo apt-get install git



Chapter 1
Getting Django Set Up Using a Functional Test

เขียน Test ก่อนเขียน Code !!

เริ่มต้นเขียน test

สร้างไฟล์ functional_test.py
โดยใส่ code ดังนี้

from selenium import webdriver

browser = webdriver.Firefox()
browser.get('http://localhost:8000')

assert 'Django' in browser.title

แล้วลอง run
$ python3 functional_test.py

จะเปิดหน้า web browser ขึ้นมาที่ http://localhost:8000 และขึ้น assertion error ใน terminal


สร้าง Django Project

$ django-admin.py startproject superlists

จากนั้น เข้าไปใน /superlists ที่เราสร้างขึ้น และ run server

$ python3 manage.py runserver

และเปิด Terminal มาเพื่อลอง run test

$ python3 functional_test.py

คราวนี้ เราจะ test ผ่านแล้ว เย้ !!



วันพุธที่ 3 กุมภาพันธ์ พ.ศ. 2559

Django Exercise Part 2

- Upload CSV File

สร้าง view ชื่อ uploadCSV ขึ้นมา เพื่อเป็นหน้าอัพโหลด โดยทำการเพิ่มเข้าไปใน /polls/views.py


และตั้งค่า URL Config สำหรับ uploadCSV() view ใน /polls/urls.py



และสร้าง template สำหรับหน้า Upload Form ขึ้นมา ชื่อว่า upload_csv.html ไปที่ /polls/templates/polls

ซึ่งหน้า template นำโค้ด HTML Form จาก w3schools มาดัดแปลง




ซึ่งจะสังเกตได้ว่า ผมได้ให้ form action ไปที่ loadcsv ซึ่งเป็น view ที่ใช้สำหรับ load csv แบบ local file เนื่องจากผมคิดว่า มันมีลักษณะการทำงานคล้ายๆกัน ไม่น่าจะต้องแก้มาก

จากนั้นเพิ่มลิ้งจากหน้า index ของ polls app เพื่อให้เข้าไปสู่หน้าอัพโหลดได้


จะได้หน้าตาเป็นแบบนี้





จากนั้น ผมได้ไปลองหาอ่านเกี่ยวกับการที่ Django จัดการกับ File ที่อัพโหลดมาจาก https://docs.djangoproject.com/es/1.9/topics/http/file-uploads/


 จุดสำคัญคือ เวลา Client ส่ง request มา ถ้ามีไฟล์ ข้อมูลจะถูกเก็บไว้ใน Attribute FILES สังเกตได้จาก request.FILES

ผมจึงลอง upload file และให้ print(request.FILES) ออกมา




สังเกตได้ว่า request.FILES จะเป็น Dictionary ที่มี key ว่า csvFile (มาจาก name ใน upload form)
ซึ่งถ้าไม่มีไฟล์ จะเป็น Empty Dictionry ( {} )

ทีนี้ ผมก็สงสัยว่าไฟล์ที่ upload มานั้น ถูกเก็บไว้ที่ไหน ในเว็บก็บอกไว้ว่า


ผมจึงไปหาวิธีเซฟไฟล์มา จนเจอลิ้งจากเว็บเดิมไป https://docs.djangoproject.com/es/1.9/topics/files/


ผมจึง import default_storage จาก from django.core.files.storage และจัดการ ทดสอบ upload file



ผมจึง import os มาเพื่อทำการเปลี่ยนชื่อไฟล์


และให้ส่วนที่อ่านไฟล์ csv อ่านจากชื่อที่กำหนดไว้ ก็จะสามารถทำการ upload คำถามจากไฟล์ csv ได้แล้ว