TAeng Trirong Pholphimai
Nellika
Published in
5 min readDec 4, 2018

--

มาเริ่มต้นเขียน Unit Test, TDD, Failing Test , Gitlab CI กันเถอะ

เข้าสู่ปฐมบท

Unit Test คืออะไร
เป็นการเขียน Test เพื่อทดสอบส่วนเล็กๆของแอปพลิเคชัน
Test Driven Development (TDD) คืออะไร
เป็นกระบวนการในการพัฒนาซอฟท์แวร์ โดยการเริ่มต้นเขียน test ก่อนจะเขียน code
Failing Test คืออะไร
เป็นการเขียน test case ที่มีผล false หรือ non successให้มันครอบคลุมก่อนที่จะเขียน test case ที่มีผล true หรือ success
CI/CD คืออะไร
Continuous Integration (CI) เป็นกระบวนการพัฒนาซอฟท์แวร์ที่มีอยู่แบบแยกส่วนกันให้ทำงานแบบต่อเนื่องกัน โดยในส่วนนี้จะมีการรัน test เพื่อทดสอบโปรแกรมของเราก่อนจะไปสู่ขั้น CD
Continuous Deployment (CD)
เมื่อโปรแกรมรัน test ในส่วนของ CI ผ่านทั้งหมด ก็จะทำการ deploy โปรแกรม

***Update
ตัวอย่าง
https://gitlab.com/t4eng/test-tdd-cicd

แนะนำตัวละคร

— Node.js
 — Mocha
 — Chai
 — babel

คาแรคเตอร์

Node.js
เป็น Cross Platform Runtime Environment ตัวหนึ่งที่เขียนด้วย JavaScript สำหรับฝั่ง Web Server

การที่จะเขียน JavaScript ฝั่ง Server ได้ก็ต้องลง Node.js ก่อน

Download ที่นี่

Mocha
เป็นตัว compiler test case บน Node.js

Chai
เป็นตัวเปรียบเทียบค่าที่เราคาดหวังว่าค่านั้นควรจะเป็นเท่านี้

Babel
เป็นตัวแปลงโค้ด JavaScript เวอร์ชั่นใหม่ ให้เป็นเวอร์ชั่นที่ Browser สามารถรันได้
เพื่อที่เราจะสามารถใช้ feature ใหม่ ๆ ของภาษา JavaScript ได้

เนื้อเรื่องเพิ่มเติม

TDD
TDD ย่อมาจาก Test Driven Development (TDD)
- เป็นกระบวนการในการพัฒนาซอฟท์แวร์ที่จะทำให้เราเขียนโค้ดเท่าที่จำเป็นเท่านั้น
- เป็นการทดสอบก่อนที่จะเขียนโค้ดให้เพียงพอเพื่อตอบสนองความต้องการ
- เป็นการเริ่มต้นเขียน test ก่อนจะเขียน code

5 ขั้นตอนง่ายๆ เหมือนนับ 1–5

  1. สร้าง test case หรือ เพิ่ม test case
    ก่อนที่เราจะเขียน test case เราควร validate ก่อนว่า ใน feature ของเรานั้นจะต้องทำอะไรบ้าง เขียนเท่าที่จำเป็นต่อ feature เรา แล้วก็เขียนสร้าง test ไว้ก่อน
  2. รัน test ให้ออกมา fail
    หลังจากสร้าง test ก็จะมา รัน test เพื่อให้รู้ว่า test caseใหม่ที่เราเขียนไปนั้นมันยังไม่มีนะ ซึ่งแน่นอนมันจะออกมา fail เพราะเรายังไม่ได้เขียนเคสนี้
  3. เขียนโค้ดให้พอรันผ่าน
    หลังจากรัน test ให้ออกมา fail ก็จะมาเขียนโค้ดให้สามารถรันผ่าน เคสไหนยังไม่มีก็ไปเขียนซะ
  4. รัน test ให้ออกมา pass
    หลังจากเขียนโค้ด ก็จะมารัน test ให้มันผ่าน ถ้าไม่ผ่านก็กลับไปเขียนให้มันผ่านดิ
  5. Refactor code
    หลักจากที่รัน code ผ่านแล้ว ก็จะมาแก้ไขโค้ดให้สวยขึ้น สั้นขึ้น หรือแก้ให้คนอื่นอ่านออกนั้นเอง เมื่อต้องการเพิ่ม test case ก็ไปทำขึ้นตอนแรกเซ่

Failing test
ยกตัวอย่าง test case แบบ Failing test ของเบอร์โทรศัพท์
เบอร์โทรศัพท์ จะไม่ถูกต้อง
- เมื่อค่าเป็น NULL ผลของ function จะออกมา false
เช่น mobileNo = null
- เมื่อค่าเป็น ว่าง “” ผลของ function จะออกมา false
เช่น mobileNo = “”
- เมื่อค่ามี ตัวอักษร ผลของ function จะออกมา false
เช่น mobileNo = 0123456Abc
- เมื่อค่ามี ตัวอักขระ ผลของ function จะออกมา false
เช่น mobileNo = 0123456#&@
- เมื่อความยาวค่า ≠10 ผลของ function จะออกมา false
เช่น mobileNo = 012345678, mobileNo = 01234567891
- เมื่อ String ตัวแรกไม่เท่ากับ 0 ผลของ function จะออกมา false
เช่น mobileNo = 9123456789

เบอร์โทรศัพท์ จะถูกต้องเมื่อ
- เมื่อมีค่าตรงข้ามกับข้างบน ผลของ function จะออกมา true
เช่น mobileNo = 0812345678

แอคชั่น

Scene 1: สร้างแอปพลิเคชัน

$ mkdir application // create folder application
$ cd application // เข้าไปใน folder application
$ npm init -y// คำสั่งสำหรับใช้สร้างไฟล์ package.json
ตัวอย่าง package.json

package.json เป็นเหมือนชีวประวัติของแอปพลิเคชันนั้น ใน package.json มันจะคอยบอกแอปพลิเคชันนั้นว่าให้ทำงานยังไง รัน script อะไร โหลด package ตัวไหนมาใช้ เพิ่มเติม

Scene 2: ลง package mocha, chai

//ลง mocha สำหรับ devDependencies โดย --save-dev
npm install mocha@^5.2.0 --save-dev
//ลง chai สำหรับ devDependencies โดย --save-dev
npm install chai@^4.2.0 --save-dev

dependencies
เป็นตัวที่เก็บชื่อ package เสริมที่เราใช้ในแอปพลิเคชัน
เวลาที่เราขึ้น production แอปพลิเคชันจะโหลด package แค่ส่วนของ dependencies
devDependencies
ส่วนใหญ่จะลง package ตัวที่ใช้ testหรือตัวที่ใช้ build ซึ่งไม่จำเป็นต้องนำ package เหล่านี้ขึ้น production

Scene 3: สร้าง folder src , สร้าง folder functions กับ folder tests ข้างใน folder src

folder functions เอาไว้เก็บ source code และ folder test จะใช้เก็บ file test

Scene 4: ลง babel
เพื่อให้สามารถเขียน JavaScript สมัยใหม่ได้ก็ต้องลง babel ก่อน

//ลง babel สำหรับ dependencies
npm install babel-register@^6.26.0
//ลง babel สำหรับ devDependencies โดย --save-dev
npm install babel-cli@^6.26.0 babel-plugin-transform-object-rest-spread@^6.26.0 babel-preset-es2015@^6.24.1 --save-dev

สร้างไฟล์ .babelrc

นำโค้ดนี้ไปใส่ใน .babelrc

{"plugins": ["transform-object-rest-spread"],"presets": ["es2015"]}

เพิ่ม scripts build ใน package.json

{
...
scripts: {

"build": "babel src -d dist --copy-files",
"test:build": "mocha dist/tests",
"test:unit": "mocha --compilers js:babel-core/register src/tests",
"start": "node dist/functions/mobileNo.js"
}
...
}

Scene 5: เขียน test case
โจทย์มีอยู่ว่าให้เขียน test case ทดสอบ function mobileNo (รับเบอร์โทรศัพท์ 10 หลัก เช่น 0812345678)

กำหนด Test Case แบบ Failing test
- เมื่อค่าเป็น NULL ผลของ function จะออกมา false
- เมื่อค่าเป็น ว่าง “” ผลของ function จะออกมา false
- เมื่อค่ามี ตัวอักษร ผลของ function จะออกมา false
- เมื่อค่ามี ตัวอักขระ ผลของ function จะออกมา false
- เมื่อความยาวค่า ≠10 ผลของ function จะออกมา false
- เมื่อ String ตัวแรกไม่เท่ากับ 0 ผลของ function จะออกมา false

- เมื่อมีค่าตรงข้ามกับข้างบน ผลของ function จะออกมา true

ก่อนที่จะเขียน Test จะขออธิบาย Pattern ของ Test ที่เราใช้กันก่อนนะครับ

describe('คำอธิบายว่าเราจะ Test เรื่องอะไร', () => {
it('คำอธิบาย Test case', () => {
// test case
})
})

ในการออกแบบ Unit Test จะประกอบด้วย 3 ส่วน
— Arrange = ทำการกำหนดค่าเริ่มต้นต่าง ๆ
— Act = ทำการเรียก function ที่ return ผลลัพธ์กลับมา
— Assert = ทำการตรวจสอบผลการทำงาน กับ สิ่งที่คาดหวังว่าตรงกันหรือไม่

มานับ 1–5 ของ TDD กัน
1.สร้าง test case ในไดเรกทอรี่ tests/checkMobileNo.js (สร้าง test case)

2.พิมพ์คำสั่ง npm run test:unit (รัน test ให้ fail)

ไม่ผ่านใช่ไหม เพราะว่าเรายังไม่ได้สร้าง class MobileNo นั่นเอง

3. สร้าง class ในไดเรกทอรี่ functions/mobileNo.js (เขียนโค้ดให้พอรันผ่าน)

อธิบายโค้ด
จะเห็นได้ว่า class MobileNo มี function validate ตรวจสอบเบอร์โทรศัพท์
ในลูป for จะเอาไว้เช็ค String แต่ละตัว ข้างในลูปจะมี if เอาไว้เช็คเงื่อนไข ถ้าเข้าเงื่อนไขใน if ก็จะ return ค่าออกมา เ
งื่อนไขในตัวอย่างข้างบนจะเอาไว้เช็คว่าถ้าหากค่าที่รับมามีตัวอักษรก็จะรีเทิร์น mobileNo is incorrect

4. พิมพ์คำสั่ง npm run test:unit ดู (รัน test ให้ผ่าน)

เย้ ผ่านแล้ว จังหวะนี้ต้องลุกขึ้นตบมือแล้วล่ะ
5. คราวนี้ก็ลองแก้ไขโค้ดสวยขึ้นดู (Refactor code)

ถ้าโค้ดที่เรา Refactor code รันผ่านแสดงว่าโค้ดที่เราแก้ไม่มีผลกระทบอะไรกับเงื่อนไขที่เราเขียน

เมื่อต้องการเขียน test case เพิ่มก็กลับไปนับ 1–5 ใหม่

เขียน Test Case ที่เหลือ
1. เพิ่ม Test Case ใน checkMobileNo.js (เพิ่ม test case)

2. พิมพ์คำสั่ง npm run test:unit (รัน test ให้ fail)
ไม่ผ่านใช่ไหม เพราะว่าเรายังไม่ได้เพิ่มเงื่อนไขใน class MobileNo นั่นเอง

3. เพิ่มเงื่อนไขใน mobileNo.js

4. พิมพ์คำสั่ง npm run test:unit (รัน test ให้ success)
5. คราวนี้ก็ลองแก้ไขโค้ดสวยขึ้นดู (Refactor code)

อธิบายสักเล็กน้อย
Regular
Expressions (regEx)
regex คือ การกำหนดรูปแบบหรือกลุ่มคำ เพื่อเอาไว้ใช้ค้นหาข้อความต่างๆ
เพื่อเช็คดูว่าตรงตามเงื่อนไข (pattern) ที่เรากำหนดไว้หรือไม่ ในที่นี้ก็คือเงื่อนไขรับเฉพาะ ตัวเลข 0–9 จำนวน 10 ตัว โดย String ตัวแรก จะต้องเป็น 0

Refactor code ได้อีกนิ

CI/CD
1. สร้างไฟล์ .gitignore

อธิบายสักนิดนึง
ถ้าเราไม่ต้องการให้ไฟล์บางไฟล์ขึ้นไป อยู่บน repository ของเรา ก็ให้ไปกำหนดใน .gitignore

2. สร้างไฟล์ .gitlab-ci.yml

อธิบายสักนิดนึง
image คือ Docker Image ในนี้ก็จะเป็น node:14-alpine
before_script คือ script ที่ต้องการให้ทำงานก่อนที่จะถึง job ต่อไป
after_script คือ script ที่ต้องการหลังจาก job ทำงานเสร็จ
cache คือ ใช้เพื่อระบุ file หรือ directory ที่ต้องการเก็บ cache ในระหว่างการทำงานของ job
stages คือ ใช้ในการกำหนดขั้นตอนของการทำงาน โดยสามารถมี stage ได้มากกว่าหนึ่ง ซึ่งในตัวอย่างจะมี stages ได้แก่ test:unit, build, test:build และ deploy
job คือ งานที่ต้องการจะทำซึ่งสามารถมีได้มากกว่าหนึ่ง และ job ในตัวอย่างก็ได้แก่ unit_test, build, build_test และ deploy_code

3. สร้าง repo gitlab ใน https://gitlab.com/projects/new

4. git command

//พิมพ์คำสั่ง
git init
//พิมพ์คำสั่ง (ถ้า config username email แล้วไม่ต้องก็ได้)
git config --global user.name "{{username}}"
git config --global user.email "{{email}}"
//พิมพ์คำสั่ง
git remote add origin git@gitlab.com:{{username}}/{{repo}}
//พิมพ์คำสั่ง
git add .
//พิมพ์คำสั่ง
git commit -m "{{comment commit}}"
//พิมพ์คำสั่ง
git push -u origin master

5. ไปที่ https://gitlab.com/{{username}}/{{repo}}/pipelines

อธิบายสักนิดนึง
จากรูปข้างบนจะเห็นได้ว่า มีทั้ง success และ failed
ถ้าหากทุก job รันออกมา success ทั้งหมด ก็จะทำงานทั้งหมด
ถ้าหากมี job ที่รันออกมา failed ก็จะหยุดที่ job นั้นทันที จะไม่ทำ job ต่อไป

6. ทีนี้กลับไป Refactor code สิ mobileNo.js

//พิมพ์คำสั่ง
git add .
//พิมพ์คำสั่ง
git commit -m "{{comment commit}}"
//พิมพ์คำสั่ง
git push -u origin master
ตัวอย่างการทำงานของ ci/cd ของ gitlab
ตัวอย่างงานที่สำเร็จทั้งหมด

เอ็นเครดิต

failing test
http://www.somkiat.cc/failing-test-in-tdd/

Test-driven development
https://en.wikipedia.org/wiki/Test-driven_development

--

--