หลักของ unit test คือ test ส่วนที่เล็กที่สุด เฉพาะ function หรือ method นั้นๆ ถ้ามีการเรียก method จาก class อื่นถือว่าเป็น dependency ให้ mock หรือ stub ทั้งหมด
คำถามคือ
1.ถ้าผมต้องการพัฒนาโมดูล ที่ต้องเรียก method จาก class อื่นดังตัวอย่างข้างล่างต้องการตรวจสอบว่าเป็น associative array หรือไม่ เราจะเขียน test ยังไงครับ
class my_class {
....public function do_something(array $input) {
........if (Arr::isAssoc($input))
............ //
........else
............ //
....}
}
2.ถ้าโมดูลที่พัฒนาประกอบด้วยหลาย class เวลาเขียน test จำเป็นต้อง mock มั้ยครับ เช่นตัวอย่างข้างบน เรามองว่ามันคือ component test ได้มั้ยครับ
3.ถ้าผมจะพัฒนา ORM หรือ ActiveRecord ขึ้นมาเอง ซึ่งคุณสมบัติของมันคือตัวช่วยในการ query ข้อมูล การ test จึงหลีกเลี่ยงการต่อ database ไม่ได้ เพราะอย่างน้อยก็ต้อง test ว่า query builder ทำงานถูกต้อง แบบนี้ยังถือว่าเป็น unit test มั้ยครับ (เพราะ unit test ห้ามต่อ database)
มือใหม่หัด TDD
ขอบคุณครับ
ในเคสของ Arr ถ้าเป็นแค่ static function ช่วยทำงานเฉยๆผมจะไม่ stub ออกครับ
ถ้าจำเป็นต้องเทสต่อ db แนะนำให้แยกเทสมาเป็นอีกชุดครับ รันแยกกัน
Blog: https://medium.com/@tanakritsai
จากข้อ 3 แปลว่าไม่ใช่โค้ดทุกแบบที่จะทำ unit test ได้ใช่ไหมครับ เช่น ถ้าผมจะพัฒนา ORM library อย่างน้อยที่สุดก็ต้องเป็น integration test แล้วใช่ไหมครับ
ขอบคุณครับ
จริง ๆ กรณีนี้ทำเป็น Dependency Injection ได้ครับ
สมมติเราทำ ORM เราก็แยกส่วน Database Driver ออกจาก ORM (พวกที่แม๊ปดาต้าเบส) จากนั้น เวลาเราเขียน Unit Test ส่วนของ DB เราก็เขียน Mock Database Driver ขึ้นมาแล้ว Inject ลงไปในฟังก์ชันนั้น ๆ ส่วนเวลาใช้งานจริงก็แค่ Inject ตัว DB Driver เข้าไปตอนใช้
เช่น ...
แล้วตอนเขียนของจริงก็ไปใช้แบบ ....
orm = new ORM(new SqliteDriver());
หรืออะไรก็ว่าไปครับปล.1 โค๊ดเป็นภาษามั่ว ๆ อาจจะดูไม่ค่อยเข้าใจ 555
ปล.2 ตัวเทคนิค Dependency Injection (คือจะใช้อะไรให้ใส่อันนั้นตอนเรียกใช้) เรียกอีกชื่อนึงว่า Inversion of Control ครับ ลองศึกษาดู
ขอบคุณครับ ผมคงต้องฝึกฝนอีกสักพักจึงจะเห็นภาพชัดเจนขึ้นครับ ไม้แก่ดัดยาก :D
ถือว่าเป็น integrated test
ขอบคุณครับ
มีอีกทางนึง ถ้าจะ test query builder
ก็คือแยก function query builder ออกมา แล้ว test เฉพาะ function นั้น
แล้วก็ตรวจสอบ output ว่าตรงกับคำตอบไหม
แล้วก็ทำ function ที่เอาผล query ไปแปลงเป็น output แล้วก็เขียน test บน fn นั้น
ส่วน function query จริงๆ ก็แค่ไปเรียก query builder แล้วก็ เรียกตัวสร้าง output ไม่ต้องให้มันมี logic อะไรมาก แล้วค่อยไปทำ test บน integration
ขอบคุณครับ
เอาจริง ๆ เรื่องการ test db นี่ ถึงจุดนึงผมก็บอกว่า ช่างแม่ง (ฮา) คือเริ่มขึ้เกียจ maintain mock ทั้งหลาย แล้วเจอว่าพอรันกับ db จริงก็ไปตายใน integration test อยู่ดี
ก็เลยใช้วิธีเทสต์บน CI แทน ที่บริษัทใช้ CircleCI ซึ่งสามารถรัน Docker Image ขึ้นมารัน DB ได้ ก็ทดสอบมันในนั้นเลย ก็จะช้าตอนรันหน่อย แต่จากเทสต์เคสที่มีตอนนนี้ก็ยังไม่ถึงกับเป็นปัญหา ตัวเทสต์ที่ช้าที่สุดในระบบตอนนี้เป็นโค๊ด networking (ผ่าน TCP) DB ยังทดสอบได้โอเคอยู่
แต่เรามีข้อแม้ว่าเราจะ wipe database ทิ้งทุกเทสต์เคส แล้วเซ็ตอัพใหม่หมด เพราะผมว่าเทสต์ที่ดีไม่ควรต้องทำตามลำดับ
ทั้งนี้ตัวเครื่อง developer เองก็จะรัน docker-compose ค้างไว้ จะมี server สำหรับทำ integration test แยกออกมา (หรือรันบนคนละ db กับตัว development) ไม่ใหมันปนกัน ไม่งั้น wipe ทิ้งทุกเคส dev ก็คงน้ำตาตก 555