Tags:

หลักของ 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
ขอบคุณครับ

Get latest news from Blognone
By: zixs
ContributoriPhoneWindows PhoneAndroid
on 22 May 2020 - 22:30 #1159567
zixs's picture
  1. เวลาเขียน unit test มันจะค่อนข้างเหมือนเราเทส blackbox เล็กๆอ่ะครับ เราจะใส่ input อะไรสักอย่างเข้าไป แล้วสนใจ output
    ในเคสของ Arr ถ้าเป็นแค่ static function ช่วยทำงานเฉยๆผมจะไม่ stub ออกครับ
  2. อะไรก็ตามที่เป็น dependency ไม่เกี่ยวข้องกับ blackbox ที่เราเทสอยู่ต้อง mock ครับ
  3. ไม่ถือว่าเป็น unit test ครับ เมื่อไหร่ก็ตามที่ต่อ io เราจะถือเป็น integration test
    ถ้าจำเป็นต้องเทสต่อ db แนะนำให้แยกเทสมาเป็นอีกชุดครับ รันแยกกัน

Blog: https://medium.com/@tanakritsai

By: crucifier
iPhoneAndroidUbuntu
on 23 May 2020 - 16:10 #1159568 Reply to:1159567

จากข้อ 3 แปลว่าไม่ใช่โค้ดทุกแบบที่จะทำ unit test ได้ใช่ไหมครับ เช่น ถ้าผมจะพัฒนา ORM library อย่างน้อยที่สุดก็ต้องเป็น integration test แล้วใช่ไหมครับ

ขอบคุณครับ

By: mr_tawan
ContributoriPhoneAndroidWindows
on 27 May 2020 - 22:28 #1160333 Reply to:1159568
mr_tawan's picture

จริง ๆ กรณีนี้ทำเป็น Dependency Injection ได้ครับ

สมมติเราทำ ORM เราก็แยกส่วน Database Driver ออกจาก ORM (พวกที่แม๊ปดาต้าเบส) จากนั้น เวลาเราเขียน Unit Test ส่วนของ DB เราก็เขียน Mock Database Driver ขึ้นมาแล้ว Inject ลงไปในฟังก์ชันนั้น ๆ ส่วนเวลาใช้งานจริงก็แค่ Inject ตัว DB Driver เข้าไปตอนใช้

เช่น ...

abstract class DBDriver {
    RowIter Query(string);
}

class Case1MockDBDriver {
    RowIter Query(string query) {
       return mockData;
    }
}


void TestORMCase1() {
    // inject ตัว MockDriver เข้าไป
    ORM orm = new ORM(new Case1MockDBDriver());
    BusinessObj obj = new BusinessObj();


    var result = orm.Map(&orm);
    if (!result) {
        Assert("failed");
    }
}

แล้วตอนเขียนของจริงก็ไปใช้แบบ .... orm = new ORM(new SqliteDriver()); หรืออะไรก็ว่าไปครับ

ปล.1 โค๊ดเป็นภาษามั่ว ๆ อาจจะดูไม่ค่อยเข้าใจ 555

ปล.2 ตัวเทคนิค Dependency Injection (คือจะใช้อะไรให้ใส่อันนั้นตอนเรียกใช้) เรียกอีกชื่อนึงว่า Inversion of Control ครับ ลองศึกษาดู


  • 9tawan.net บล็อกส่วนตัวฮับ
By: crucifier
iPhoneAndroidUbuntu
on 28 May 2020 - 20:35 #1160531 Reply to:1160333

ขอบคุณครับ ผมคงต้องฝึกฝนอีกสักพักจึงจะเห็นภาพชัดเจนขึ้นครับ ไม้แก่ดัดยาก :D

By: blackdoor on 23 May 2020 - 15:00 #1159610
blackdoor's picture

ถ้ามีการเรียก method จาก class อื่นถือว่า ...

ถือว่าเป็น integrated test

  1. ต้องกำหนด input value แล้วก็ expected ค่าที่ต้องการครับ
  2. จำเป็นต้องมี mock ครับ เพราะ unit test ควรจะ test แค่ method และการทำงานของมัน ไม่ควรไปยุ่งเกี่ยวกับส่วนอื่นๆ ซึ่งมันจะกลายเป็น integrated test มากกว่า unit test
  3. ก็ทำ mock ของ model หรือ entity สำหรับ test ครับ จะไม่ดีแน่ ๆ ถ้าทุก ๆ ครั้งที่ run test แล้วต้องไป connect กับ db
By: crucifier
iPhoneAndroidUbuntu
on 23 May 2020 - 16:10 #1159626 Reply to:1159610

ขอบคุณครับ

By: iamfalan
iPhoneAndroidWindows
on 25 May 2020 - 13:07 #1159801

มีอีกทางนึง ถ้าจะ test query builder
ก็คือแยก function query builder ออกมา แล้ว test เฉพาะ function นั้น
แล้วก็ตรวจสอบ output ว่าตรงกับคำตอบไหม
แล้วก็ทำ function ที่เอาผล query ไปแปลงเป็น output แล้วก็เขียน test บน fn นั้น
ส่วน function query จริงๆ ก็แค่ไปเรียก query builder แล้วก็ เรียกตัวสร้าง output ไม่ต้องให้มันมี logic อะไรมาก แล้วค่อยไปทำ test บน integration

By: crucifier
iPhoneAndroidUbuntu
on 25 May 2020 - 15:09 #1159827 Reply to:1159801

ขอบคุณครับ

By: mr_tawan
ContributoriPhoneAndroidWindows
on 31 May 2020 - 17:08 #1160769
mr_tawan's picture

เอาจริง ๆ เรื่องการ 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


  • 9tawan.net บล็อกส่วนตัวฮับ