Define Test

อาทิตย์ที่ผ่านมาเริ่ม Push TDD ให้ทีม Dev ทั้งทีมก็มีเสียงตอบกลับมาทันที ซึ่งเป็นสิ่งที่อยากได้และจะได้เอาไปปรับปรุง เสียงตอบอันนึงที่กลับมาถูกบล๊อกโดย @teerapapc ผ่านหัวข้อนี้ “My experience with unit testing in software development” สรุปสั้นๆ คืออยากให้คนอื่นเป็นคนเขียน Test ในตอนท้ายของบทความ

จริงๆ เรื่องเขียน UnitTest สำหรับ Dev หลายคนที่พึ่งจบออกจากมหาวิทยาลัยและเขียนโปรแกรมเล่นตามสิ่งที่สนใจ มักเป็นสิ่งที่อยู่ห่างไกลกันเหลือเกิน แม้แต่ตัวเองตอนจบใหม่ๆ และทำงานได้สองสามปีแรก ก็ไม่รู้ว่าจะเขียน Test ยังไงเพราะขั้นตอนการทำงานเริ่มจาก

  1. ได้ Requirement มาจาก Manager
  2. จากนั้นก็เริ่มเขียน Module ต่างๆ ตามที่คิดไว้
  3. ลองรันดูว่ามันทำงานถูกต้องตามที่ Manager ให้มามั้ย
  4. ปิดเคส

ด้านบนคือช่วงก่อนที่จะมี QA ช่วงที่มี QA เข้ามาก็จะมีขั้นตอนเพิ่มก่อนปิดเคสคือ ส่งให้ QA เล่น จากนั้นงานก็จะเด้งเป็นปิงปองซักพัก ก่อนจะปิดได้ (บางงาน ปิงปองระหว่าง QA – Dev หลายคนด้วย)

ถ้าเป็นระบบที่ทำใหม่หมดขั้นตอนก็เปลี่ยนไปนิดหน่อยคือ

  1. ได้ Requirement มา
  2. หาคำนามใน Requirement แปลงออกมาเป็น Model ต่างๆ
  3. เขียนตาม Model นั้นพร้อมกับสร้าง Module ตาม Behavior ของแต่ละคนแยกกันไป
  4. ส่งไปให้ QA เล่น
  5. เล่นปิงปองระหว่าง QA กับ Dev อีกครั้ง
  6. ปิดเคส

สิ่งที่หายไปจากขั้นตอนด้านบนทั้งสองอันคือการกำหนดการกระทำต่างๆ ของระบบที่ได้จาก Requirement เช่นระบบบัญชี ก็จะมีขั้นตอนการทำงานต่างๆ ตั้งแต่การนำตัวเลขมาป้อนเข้าระบบ จากนั้นตรวจสอบว่าสิ่งที่ใส่เข้ามาอยู่ในรูปแบบที่ถูกต้องมั้ย ถึงเอาไปประมวลผลบางอย่างเพื่อให้ได้ผลลัพธ์ออกมาตามที่กำหนด

จากขั้นตอนด้านบนส่วนใหญ่จะถูก Dev ลัดขั้นตอนไปเขียนโปรแกรมตามพฤติกรรมเลย ไม่สนใจการกำหนด input/output ขึ้นมาก่อน เพราะคิดว่า function หรือ class ที่เราเขียนตัวฟังก์ชั่นคือการ define input และ Return ก็คือ output ของระบบอยู่แล้ว พองานมันซับซ้อนมากขึ้น เราก็พยายามแยกงานบางส่วนออกไปอีก function อีก class โดยที่ไม่สนใจ define input/output และหน้าที่ของแต่ละชั้นให้ชัดเจนอีก สุดท้ายกลายเป็นว่า การทำงานมันกระจายข้ามกันหลายชั้นมาก จะทดสอบชั้นย่อยๆ ก็ทำได้ยาก เลยต้องไปทำจากด้านนอก ทดลองรันทุกชั้น เพื่อให้ได้ผลลัพธ์ออกมาจาก output ที่หลากหลาย ทั้งๆ ที่ถ้าทดสอบแต่ละชั้นแล้ว input ที่มีจะน้อยกว่าทุกชั้นรวมกันมาก

เอาหละ ในเมื่อลัดขั้นตอนกันหมดแล้ว ไม่มีการกำหนด Spec กันมาก่อน การจะมาแก้สิ่งที่เคยทำไปแล้วจะให้คนนอกมาเขียนโดย Define input/output ของคนอื่นก็คงทำไม่ได้ เพราะถึงทำได้คนนอกก็ไม่รู้ขอบเขตของแต่ละชั้นที่ Dev เป็นคนทำอยู่ดี คนที่ควรจะรู้ว่าแต่ละชั้นทำงานอย่างไรมากสุด ก็ควรจะเป็นคนที่สร้างสิ่งเหล่านั้นขึ้นมา ไม่งั้น Dev ก็ไม่ควรจะสร้าง function ขึ้นมาได้ทั้งๆ ที่ไม่รู้ input และสิ่งที่ตัวเองต้อง return ออกมา แต่ปรากฏว่าหลายๆ ครั้ง Dev ลืมว่าตัวเองสร้างอะไรไป ทำงานยังไงเพราะเวลามันผ่านไปนานพอสมควร ก็ต้องค่อยๆ ไล่สิ่งที่ตนเองเขียนขึ้นมา บางครั้งก็บอกไม่ได้ว่าตัวเองทำอะไรไปในตอนนั้น ทำไปทำไม แถมไม่มีอะไรบอกอีกว่า input อะไรส่งมาจากชั้นก่อนหน้า และ return อะไรออกไป สุดท้ายก็ปล่อยมันไปและก็สมมุติว่ามันทำงานถูกต้องต่อไป

บ่นมาเยอะเกินครึ่งหน้าจริงๆ อยากจะเขียนแค่ว่าการจะเขียน test ให้สนุก ง่ายไม่ซ้ำซ้อนกับการเขียน code จริงๆ แล้วไม่ยากเลยเพียงแต่ต้องกำหนด input/output ของสิ่งที่เราจะ test เท่านั้นเอง ระบบใดๆ ส่วนย่อยสุดประกอบด้วยสามอย่าง คือ input/process/output ส่วนย่อยบางส่วนอาจจะไม่มี input บางตัว input อาจจะลึกลับหน่อยเป็นตัวแปรกลางที่หลายคนเรียกว่า state แต่ถ้าเรากำหนด input/output ได้ครบแล้ว สิ่งที่ Dev เขียนก็ไม่ควรจะมีอะไรลึกลับที่ทำงานแล้วได้สิ่งที่เราไม่คาดคิดอีก ถ้าระบบใดมี output ที่เราไม่เคยเจอ แปลว่าสิ่งที่เราทดลองต้องมี input บางอย่างที่ขาดหายไป

วิธีการหา input พวกนี้ส่วนใหญ่จะได้มาจากตอนเรียนในมหาวิทยาลัยนั่นแหละ ตั้งแต่การเขียน State diagram, Sequence diagram, … diagram ทั้งหลาย การเขียน diagram พวกนั้นก็คือการหาหน้าที่ และ input/output ของระบบที่เราจะทำนั่นแหละ เราใช้อันไหนแล้วเห็นภาพมากกว่ากันก็เลือกอันนั้น สำหรับผม diagram ที่เขียนแล้วเห็นภาพสุดก็คงเป็น Sequence เพราะมันเห็นชั้นต่างๆ และสามารถคิดและกำหนดหน้าที่แต่ละชั้นได้ บางคนอาจจะเป็น State เพราะจะได้กำหนด input ที่ถูกซ่อนอยู่ให้หมด จริงๆ อีกวิธีที่นิยมในปัจจุบันก็คือการเขียน test หรือกำหนด behavior ขึ้นมาเป็นภาษาโปรแกรม ก็เป็นการ define input/output อย่างนึง ก็แล้วแต่คนชอบ แต่ยังไงก็ต้องมีการ define พวกนี้มาก่อนที่จะเขียนโปรแกรมขึ้นมาให้ทำงาน การกำหนดพวกนี้ จะเป็นคนที่เขียน code เองเป็นคนทำหรือให้คนอื่นเป็นคนทำ แล้ว Dev ก็เป็นแค่ labor เขียน code ตามคำสั่งก็ได้แล้วแต่ความถนัดของแต่ละคน แต่ถ้า Dev เป็นคนที่ต้องคิดถึงหน้าที่และขั้นตอนการทำงาน Dev เป็นคนเขียนเองนั่นแหละจะดีที่สุด จะได้ตรงใจ Dev เองไม่ต้องเล่นปิงปองระหว่าง Manager/Dev อีก

About llun

Just a programmer

, ,

  • Pingback: Backup My Bookmark Table | MzSoft

  • http://twitter.com/hybridknight hybridknight

    //man … เข้ามาดู

  • http://www.sourcode.net sourcode

    *เดินผ่าน*

  • http://twitter.com/teerapapc teerapapc

    โอย มัน ตัด newline ทิ้ง หมดเลย  อ่านยากหน่อยนะครับ :P

  • http://twitter.com/teerapapc teerapapc

    พี่สรุปสั้นไปหน่อยปะครับ หะหะ ว่า ผมอยากให้คนอื่นเขียนแทน จริงๆอันนั้นเป็นข้อสรุปของ ข้อ 3 ย่อยของผมเท่านั้นเองแต่ที่พี่เขียนมา เรื่องการกำหนด input / output มันก็ไม่ได้ตอบปัญหาทั้ง 3 ข้อที่ผมเขียนเท่าไหร่อยู่ดี ผมรู้ว่า module นี้ ควรจะมี input /output อะไร แต่ผมขี้เกียจจะเขียนโค้ด simulate มัน (นี่คือข้อ 3)จากข้อ 2 ของผม พบว่าส่วนใหญ่มันพังตอน integration test เพราะฉะนั้น จะเขียน test โค้ด ละเอียดยิบย่อยระดับทุกๆ class เลย ไปทำไม เขียน test คลุมเป็นกลุ่มๆ ก็น่าจะพอ ดู input / output ระดับนอกสุด ก็พอการกำหนด input / output ของทุกคลาส แล้วมาเขียน test แยกกันหมด มันเหนื่อย และเยอะเกินไป ทั้งๆที่ เวลาโค้ดทำงานจริงๆ มันต้องทำงานร่วมกับ class อื่นอยู่ดี แล้วถึงพี่เขียนละเอียดไป พี่ก็ต้องเขียน integration test อีกอยุ่ดี  การเขียนละเอียดทำให้หาสาเหตุ class ตัวปัญหาเร็วขึ้นเท่านั้น อย่างที่เขียนไว้ใน blog ผม มันเป็นเรื่อง การ define ว่า unit ของโปรแกรมเรา เล็กแค่ไหนน่ะครับแล้วผมก็กลับคิดว่า คนอื่น เขียน test ให้อาจจะดีกว่าด้วย เพราะเป็นการ recheck requirement ไปด้วยกันด้วย เพราะถ้าผมเขียน test เอง ก็จะเขียนตาม requirement ที่ผมเข้าใจ ซึ่งอาจจะไม่ถูกก็ได้ แต่ test ผ่าน แต่คนอื่นเขียน เค้าก็เขียนตาม requirement ที่เค้าเข้าใจ ผมก็เขียนโค้ดตาม requirement ที่ผมเข้าใจ  เอ่าไม่ผ่าน ก็เจอว่า เข้าใจอะไรไม่ตรงกัน

    • http://llun.in.th/ llun

      ฮ่า จะบอกว่าสรุปสั้นไปก็ได้แหละ แต่เอามาจากในบล๊อกนั้นเลยนะ :P

      แต่มันต้องเริ่มมาจาก

      1. Requirement ที่ได้มามี Behavior อย่างไรบ้าง แล้วก็ตีออกมาดูว่ามี Module/Class อะไรบ้างที่ต้องใช้ โดยแต่ละ Module/Class มีหน้าที่อะไร (ไม่ควรจะซ้ำกันเลย และชัดเจน)
      2. เมื่อได้หน้าที่ของ Module/Class แต่ละชั้นที่ชัดเจนแล้วถึงมาเขียน Test ครอบแต่ละชั้นเนื่องเพื่อให้ยังคงความชัดเจนของมันอยู่

      คราวนี้ตอบคำถามแรกคือความละเอียดของ Test ว่ามันควรจะทำละเอียดแค่ไหน อันนี้ถึงจะเคยบอกว่าแล้วแต่ แต่จริงๆ อยากให้ทำระดับ Class เพราะว่า

      1. จะได้ความชัดเจนในหน้าที่ของ Class นั้นว่ามันทำอะไร
      2. เพื่อทำให้รู้ว่าเราทำอะไรซ้ำกับชั้นอื่นๆ หรือ Class อื่นหรือป่าว

      จริงๆ จะเปลี่ยนจาก Class เป็น Module หรือ Component ก็ได้แต่สิ่งที่จะเสียไปคือความชัดเจนภายในไป

      ส่วนเรื่องให้คนอื่นเขียน Test ก็อย่างที่เขียนไว้ด้านบนคือ คนที่จะบอกได้ว่าตรงหรือไม่ตรงตาม Requirement มีสองคนคือ 1 คนที่ให้ Requirement กับ 2 คนที่แปลง Requirement นั้นออกมาเป็นขั้นตอนการทำงาน คราวนี้ในขั้นตอนการทำงานมันมีส่วนย่อยๆ ที่เป็นข้อจำกัดอีก ซึ่งคนทำจะเป็นคนรู้และต้องเอาไปต่อลองว่าทำไม่ได้ และจะกลายเป็นคนให้ Requirement เองด้วย เพราะงั้น คนทำนั่นแหละ เลยต้องเป็นคนเขียน Test กำหนดสิ่งที่ทำไม่ได้และบอกให้คนออก Requirement คนแรกรับรู้ด้วย