คุณว่าอยากจะเขียนยูนิตเทสรึ

By on Sep 14, 2008 in translated | 4 comments

Share On GoogleShare On FacebookShare On Twitter

เรื่อง : คุณอยากจะเขียนยูนิตเทสอย่างนั้นรึ
Translated from : So You Wanna Write A Unit Test
Author : Russ Olsen
Thanks to Russ who allow me to translate his blog here

Russ is also the author of Design Patterns in Ruby

ผมเห็นนักพัฒนา (developers) หลายๆคน มองการเขียนยูนิตเทส ในทำนองเดียวกับที่ นักบุญออกัสตินคิดต่อการสำรวมในกาม: ข้าแต่พระเจ้า ประทานพลังแก่ข้าในการเขียนยูนิตเทสเถิด, แต่ยังก่อนอย่าพึ่งเป็นตอนนี้. เรารู้ดีว่าเราควรจะเขียนยูนิตเทส แต่เราก็คอยแต่จะผัดผ่อนมันเรื่อยมา ต่อไปนี้เป็น 5 ประการที่พึงท่องไว้ในใจ ขณะที่คุณพยายามสร้างความมุ่งมั่นในการเขียนเทส

1. อย่าเสียเวลาไปทั้งวัน
ยูนิตเทสที่ใช้เวลาประมวลผลหนึ่งชั่วโมง จะถูกประมวลผลอย่างมากสุด ชั่วโมงละหนึ่งครั้ง นี่ไม่ได้ล้อเล่นนะ ยูนิตเทสที่ใช้เวลาประมวลเป็นชั่วโมง จะถูกใช้งานหนึ่งหรือสองครั้งต่อสัปดาห์ถ้าคุณโชคดี ถ้าคุณอยากให้นักพัฒนาใช้งานยูนิตเทสของคุณละก็ มันจำเป็นจะต้องเร็ว และสามารถประมวลผลอย่างอัตโนมัติ และมันต้องไม่กินเวลาในการเตรียมการ (setup) สองชั่วโมงหรือแม้แต่แค่สองนาที

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

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

ถ้าหากเทสนั้นไม่ผ่านผมก็จะพึ่ง debugger เพื่อนยาก หรือใส่คำสั่งบันทึกการทำงาน (logging) ของผมเอง. ผมมันคนง่ายๆประเภทแค่ ใช่ หรือ ไม่ใช่

3.เลิกดักจับ exception.
คำถาม : อะไรคือข้อแตกต่างระหว่าง JUnit เทส นี้ :

public void testWidget() throws WidgetException {
    Widget w = new Widget();
    w.doSomething();
}

กับเทสนี้

public void testWidget() {
    try {
      Widget w = new Widget();
      w.doSomething();
    }
    catch( WidgetException we) {
      fail("widget failed", we );
    }
}

ข้อแตกต่างก็คือ, WidgetException ควรจะถูกโยนออกไปหรือไม่, โค้ดอันแรกจะประมวลผลไม่ผ่านพร้อมๆกับแสดง stack trace ชี้ไปยังจุดที่ประมวลผมผิดพลาด โค้ดอันที่สองจะเกิด exception บอกว่าเทสนั้นไม่ผ่านเหมือนกัน แต่มันจะแสดง stack trace ชี้กลับไปยังคำสั่ง fail() ข้อมูลจริงๆที่คุณต้องการเพื่อจะรู้ว่าเกิดอะไรขึ้นกันแน่ ถูกฝังไว้ที่ไหนสักแห่งหนึ่งใน stack trace ที่ซ้อนไว้อีกชั้น พูดง่ายๆคือตัวอย่างแรกนั้นไม่เพียงแต่จะกระชับกว่า แต่ยังทำงานดีกว่าอีกด้วย

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

โปรดจำว่าถ้าหากไม่ใช่เทสเกี่ยวกับการจัดการ exception (exception handling) ที่ซับซ้อน เมื่อใดที่เทสโยนส่ง exception ออกไปเมื่อนั้นงานของคุณได้จบลงแล้ว เก็บเอาความกระตือรือล้นในการจัดการ exception สำหรับครั้งต่อไป ในคราวที่มันจะทำให้โปรแกรมของคุณดีขึ้น

4. อย่าเขียน “สงครามและสันติภาพ (War And Peace)” ของเทสเมธอด
ทำไมทุกครั้งเมื่อวิศวกรบางคนเริ่มต้นเขียนยูนิตเทส เขาจะต้องย้อนเวลากลับไป เมื่อตอนราตรีฉลองจบการศึกษา (prom night) โปรแกรมเมอร์ธรรมดา ท่าทางเรียบร้อยคนหนึ่ง ผู้ไม่เคยคิดจะเขียนเมธอดยาวถึงขนาด 60 บรรทัด มาก่อนเลยในชีวิต กลับมึนเมาพ่นเอา เทสเมธอดยาว 1700 บรรทัดออกมา แบบไม่มีคอมเมนต์ ไม่มีโครงสร้าง ไม่เห็นแนวความคิด จริงที่ข้อกำหนดต่างๆย่อมแตกต่างออกไปเมื่อคุณเขียนยูนิตเทส ไม่, คุณไม่ต้องทำตามข้อตกลงไปเสียทั้งหมด และก็ไม่ต้องระวังไปเสียทุกอย่าง. แต่ขอเพียงแค่สงสารพวกเราคนที่ต้องมานั่งจัดการกับสิ่งที่คุณทำไว้บ้าง

ถ้าคุณเขียนเทสเมธอดขนาดใหญ่ ที่ไม่มีแบบแผนอะไรเลย แล้วสามารถประมวลผลได้ปกติ มันก็เยี่ยม แต่ถ้าหากมันเทสไม่ผ่านละ จุดผิดพลาดที่บรรทัด 1543 มักจะหายากกว่าจุดผิดพลาดที่ 43

5.อย่าล้มเลิกความตั้งใจ
ไปที่ร้านหนังสือที่ไหนก็ได้ หรือค้นหาในเวบไซท์ทางวิศวกรรมซอฟแวร์ ที่เวบไหนก็ได้ มันไม่เป็นการยากเลยที่จะเจอคำพูดเป็นพันๆคำ เกี่ยวกับการเขียนยูนิตเทสให้ครอบคลุมทุกด้าน สำหรับทุกๆ เส้นทางการประมวลผล (execution code path)

ในความเป็นจริงบางครั้งไม่มีเวลามากพอ หรือไม่มีความเข้าใจในโค้ดที่เราพยายามจะเทส จะทำอย่างไรละ? อืม ก็เขียนยูนิตเทสอะไรก็ได้ เท่าที่คุณจะเขียนได้นั่นละ. อย่างน้อยที่สุด เขียนแค่ยูนิตเทสที่ใช้งานโค้ดสักนิดหน่อย โดยไม่ต้องทำการทดสอบสมมุติฐาน (assertion) เลย ตัวอย่างเช่น.

public void testWidgetCreation() {
    Widget w = new Widget();
}

เทสข้างบนนั้น แม้จะห่างไกลจากทที่ควรจะเป็นมากนัก แต่มันก็ได้เทสในหลายสิ่งมากกว่าที่คุณคิด ถ้าหากว่า testWidgetCreation() สามารถผ่านเทสได้ คุณจะสามารถรู้ได้ว่า:

ไม่เลวนักสำหรับโค้ดบรรทัดเดียว ผมขอย้ำอีกครั้งว่า จงเขียนยูนิตเทสที่ดียอดเยี่ยมถ้าคุณสามารถเขียนได้หรือไม่ก็เขียนยูนิตเทสแค่ระดับ OK ถ้านั่นคือดีที่สุดที่คุณทำได้ ถ้าคุณทำอย่างอื่นไม่ได้แล้ว อย่างน้อยเขียนยูนิตเทสที่ใช้งานโค้ดสักนิดหน่อย ถ้าเป็นเรื่องของยูนิตเทสละก็ คำว่านิดหน่อยนั้นดีกว่าไม่มีเลยมากนัก.

เอาละในที่สุดเราก็ได้มันมา: เขียนยูนิตเทสที่กระชับที่สุดที่สามารถบอกคุณได้อย่างรวดเร็วว่าโค้ดของคุณทำงานได้หรือไม่ ยูนิตเทสที่ส่องทางไปยังปัญหาไม่ใช่ทำให้ลางเลือน ยูนิตเทสที่อย่างน้อยที่สุดก็มีอยู่จริงให้ใช้.

Translation Note :