Last updated on July 15, 2015
ภาษาโปรแกรมสมัยใหม่ (หมายถึงยุคนี้ซึ่งอาจจะมีอายุ 10-20 ปีแล้วก็ได้) บางภาษาทุกวันนี้มักจะ built-in ไลบรารี่ unit testing framework มาให้อยู่แล้วอย่างเช่น Golang Python เองก็มีมาให้ด้วยเหมือนกัน
โพสต์นี้ผมอธิบายเป็น step ตามนี้ละกันครับ
1. เตรียมเครื่องมือที่จำเป็นก่อน
เนื่องจากผมใช้ Ubuntu ฉะนั้นเครื่องไม้เครื่องต่างๆ จะเป็นทางฝั่งลินุกซ์นะครับ OS อื่นๆ ก็หามาติดตั้งเองครับไม่น่ายากสำหรับ developer อย่างเรา
1.1 ติดตั้ง Editor/IDE
มีให้เลือกใช้เยอะแยะตั้งแต่ editor ธรรมดาและ IDE ซึ่งมีเครื่องไม้เครื่องมีในการพัฒนามาให้ตั้งแต่เริ่มต้น เลือกใช้กันตามสะดวก ใครใช้ editor ก็เลือกที่ highlight โค้ดได้ก็ดีครับจะได้ดูง่ายๆ
ในตัวอย่างนี้ผมจะใช้ editor ธรรมดาคู่กับการรันคำสั่งจาก terminal ครับ
1.2 ติดตั้ง Python
สำหรับ Python Ubuntu มีให้อยู่แล้วไม่ต้องติดตั้งเพิ่มอีก ใช้ Python 2 หรือ Python 3 ก็ได้ครับ
ถ้าใช้ Ubuntu อยู่ ข้ามไปเลยก็ได้ครับ
1.3 [Optional] ติดตั้ง Pylint
ภาษาโปรแกรมแต่ละภาษาจะมีมาตรฐานการเขียนของมันอยู่ เช่น การย่อหน้า, การตั้งชื่อตัวแปร, การเว้นวรรค-บรรทัด, การเขียน document ประกอบคลาส, เมธอด
Python เองก็มีเช่นกันครับ Pylint นี้จะช่วยตรวจสอบว่าโค้ดของเราถูก format มาตรฐานตามที่ develop ทั่วไป (โครงการ Open source) เขายอมรับหรือไม่, ช่วยหาตัวแปรที่ไม่ถูกใช้, ดูว่าคลาส/ฟังก์ชันของเราซับซ้อนขนาดไหน แนะนำว่าควรใช้ครับโค้ดของเราจะได้สวยงามขึ้น
การติดตั้ง Pylint เนื่องจากยังไม่มี package ใน repository ของ ubuntu ให้เราติดตั้ง PIP ซึ่งเป็น package installation ของ Python ก่อนจากนั้นค่อยติดตั้ง Pylint ครับ
sudo apt-get install build-essential python3-dev python3-pip sudo pip install pylint
หมายเหตุ: การติดตั้งด้วย pip เป็นการดาวน์โหลดซอร์สโค้ดมาคอมไพล์แล้วติดตั้งหากเกิดปัญหาติดตั้งแล้ว error ให้ติดตั้ง package build-essential และ python3-dev ไปด้วย
1.4 [Optional] ติดตั้ง Nosetest3
ช่วยในการรันเทสให้ง่ายขึ้น ไม่ต้องพิมพ์คำสั่งยาวๆ อย่าง python -m unittest [PATH.TO.MODULE] อีกต่อไปแค่สั่ง nosetest3 มันจะวิ่งหาไฟล์ที่มีเทสเคสอยู่ให้
Nose สำหรับ Python3 มีอยู่ใน repository แล้วติดตั้งผ่าน APT ได้เลย
sudo apt-get install python3-nose
2. สร้างไดเรกทอรีของโปรเจกต์
ให้สร้างโครงสร้างไดเรกทอรีตามนี้ครับ
-project/ -lib/ -tests/ -main.py
ที่จำเป็นคือ lib/ และ tests/ โครงสร้างไดเรกทอรีอย่างน้อยๆ ก็เป็นไปตามนี้ครับส่วนใครจะเพิ่ม bin/ doc/ data/ อะไรก็แล้วแต่ครับหน้าที่ของแต่ละไดเรกทอรีก็ตามแต่เราจะแยก เช่น
- lib/ สำหรับคลาส/ฟังก์ชันของโปรแกรม
- tests/ สำหรับเทสที่เราเขียนขึ้นมา
- bin/ สำหรับไบนารีไฟล์หรือสคริปต์ที่ช่วยอำนวยความสะดวกต่างๆ
- doc/ สำหรับ documents ของโปรแกรม
- data/ สำหรับเก็บ data ภายนอกที่ใช้ในโปรแกรม เช่น ไฟล์ข้อมูลแบบ text, ไฟล์ฐานข้อมูล
สำหรับไดเรกทอรีที่เราจำเป็นต้อง import เข้ามาใช้งาน เช่น ไดเรกทอรี lib/ จำเป็นต้องมีไฟล์ __init__.py ด้วยเสมอครับ เป็นสร้างเป็นไฟล์เปล่าก็ได้ (รายละเอียดอื่นๆ ลองไปหาอ่านดูเองนะ) ก็จะได้โครงสร้างของไดเรกทอรีประมาณนี้
-project/ -lib/ -__init__.py -awesome.py -tests/ -awesome_test.py -main.py
3. เริ่มต้นด้วยการเขียนเทสก่อน
เสร็จแล้วก็เริ่มเขียนเทสก่อนเลยครับ
tests/awesome_test.py
import unittest from lib import awesome class AwesomeTest(unittest.TestCase): def setUp(self): self.awesome = awesome.Awesome() def test_printHelloShouldReturnHelloWorld(): expect = 'Hello World' actual = self.awesome.hello() self.assertEqual(actual, expect) if __name__ == '__main__': unittest.main()
*หมายเหตุ: ชื่อเมธอดต้องขึ้นต้นด้วย test_ เสมอ unittest จึงจะมองเห็น
เขียนโค้ดเสร็จอย่าลืมรัน Pylint ดูด้วยนะครับว่ามีตรงไหนเขียนไม่ถูกมาตรฐานหรือเปล่าโดยใช้คำสั่ง
pylint tests/awesome_test.py
เสร็จแล้วลองรันเทสดูครับ ซึ่งแน่นอนว่ามันจะ fail แดงเถือกเลย (แหงหละ) ให้เราแก้ตามที่ error มันฟ้อง การรันเทสใช้คำสั่ง
cd /PATH/TO/project python -m unittest tests.awesome_test
หรือถ้าต้องการเทสทั้งไดเรกทอรี tests/ ก็สั่งแบบนี้
python -m unittest tests
หรือถ้าใช้ Nosetest ก็รันคำสั่งได้เลย
nosetest3
4. เขียนโค้ดเพื่อทำให้ error ที่เกิดขึ้นหายไป
lib/awesome.py
class Awesome(): def hello(): return 'Hello Worl'
จากตัวอย่างนี้คลาส Awesome มีเมธอดเดียวคือ hello() ซึ่งจะ return string ‘Hello World’ ออกมา
5. รันเทสอีกครั้ง
จากข้อ 4 ผมตั้งใจวางบั๊กไว้ คือลบตัว ‘d’ ในคำว่า ‘World’ ออกไปเพื่อให้เทสไม่ผ่าน เมื่อเรารันเทสจะพบว่าเกิด error ขึ้นเพราะสิ่งที่เราคาดไว้ (expect) กับความจริงที่เกิดขึ้น (actual) มันไม่เท่ากันจะเกิด error ประมาณนี้ออกมาครับ
..F. ====================================================================== FAIL: printHelloShouldReturnHelloWorld (tests.awesome_test.AwesomeTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "/PATH/TO/project/tests/awesome_test.py", line 8, in printHelloShouldReturnHelloWorld self.assertEqual(actual, expect) AssertionError: Strings differ: 'Hello Worl' != 'Hello World' ... - 'Hello Worl' ? + 'Hello World' ? ^ ---------------------------------------------------------------------- Ran 1 test in 0.002s FAILED (failures=1)
assert method ไม่ได้มีแค่ assertEqual นะครับยังมี method อื่นให้ใช้อีกเยอะเลย เข้าไปดู assert method ทั้งหมดใน document ของ module unittest ได้ครับ
6. ถ้ามีบั๊กอยู่ให้แก้ไขแล้วเทสใหม่จนกว่าจะผ่าน
ให้เราแก้ไข hello() ให้ถูกโดยการเพิ่ม ‘d’ เข้าไป แล้วรันเทสอีกครั้ง เทสควรผ่านและมีข้อความตามนี้ออกมา
.... ---------------------------------------------------------------------- Ran 1 test in 0.008s OK
เสร็จแล้วครับ สนุกเหมือนเล่นเกมเลยเนอะ
เวลาจะเอาโค้ดไปใช้งานเครื่องอื่นหรือคนอื่นเอาไปพัฒนาต่อก็รันเทสดูก่อนถ้า fail ก็แก้เป็นเคสๆ ไปจนผ่านครับ