Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ทำไม Test Coverage ลดลง และวิธีกู้คืนมัน

เขียนโค้ดง่ายกว่าเขียนทดสอบ และ method หนึ่งบรรทัด จริงๆ ต้องทดสอบไหม? มัน trivial! ทดสอบเพิ่มจะ double/triple dev time และครั้งหน้าเปลี่ยนโค้ด ต้องเปลี่ยนทดสอบด้วย มองเหมือน waste โดยเฉพาะตอนเหลือเวลาน้อยใน estimate

แตเร็วๆ นี้ โค้ด covered by tests แค่ 20% การเปลี่ยนโค้ดรู้สึกเหมือนเปลี่ยนชั้นกลาง house of cards โดยไม่ทำให้พัง ที่ไหนบางที่ผิด แม้ decisions ตอนนั้นรู้สึกถูก ยังจบกับ codebase unmaintainable

นี่ไม่ใช่ skill failure เกิดกับ devs ดีที่สุด มันคือ process failure ด้วยการเปลี่ยนแปลงเล็กน้อยตอนเขียน features ใหม่ คุณปกป้องโค้ดโดยทดสอบไม่ช้า ทดสอบทำให้โค้ดเข้าใจง่ายและยืดหยุ่น เปลี่ยนโค้ดมั่นใจ ทุก path ทดสอบแล้ว

ไม่ควรต้อง決策

ถ้านั่งคีย์บอร์ดพยายาม 決策 ว่าโค้ดบิทต้องทดสอบไหม คุณผิดทางแล้ว ควร default "Test it!" แม้ดู trivial เขียนทดสอบ

ถ้าโค้ด trivial เขียน ทดสอบควรง่าย และ complicated code จะไม่ดู trivial เท่าตอนเพิ่งเขียน** ยังไงรู้ว่าอีก 6 เดือนจะดู trivial เหมือนกัน?

แตไม่อยาก overtest

Test suite ยักษ์เป็นปัญหา Tests 20 นาที = no tests เพราะไม่รันตลอด (บอกว่าจะรัน ผมรู้ว่าไม่) แย่กว่า brittle tests เยอะ ทำ refactor ปวดกว่าเดิม ไม่ทำ methods ยาวกว่านิยาย

ขัดแย้งจุดก่อนหน้า? ไม่จำเป็น Tests ควรโฟกัส interface ของโค้ด ไม่ใช่ implementation เช่น:

class Cart
  def initialize(item_params)
    @line_items = Array(item_params).map {|item| LineItem.new(item[:name], item[:price])}
  end
  def total
    @line_items.sum(&:price)
  end
end

รู้สึกว่าต้องทดสอบ Cart และ LineItem ทั้งคู่ แต LineItem 用โดยอะไรอื่นไหม? ถ้าแค่ implementation detail ของ Cart ไม่ exposed ไปนอก ต้องการทดสอบเยอะไหม? ทดสอบ ผ่าน Cart class ไม่ได้ไหม?

Classes ที่ extracted โดย refactoring มักไม่ต้อง test suite ของตัวเอง มันแค่ implementation detail ตอน用เองถึงต้องการทดสอบเพิ่ม

ด้วย great test suite ต่อ public interface คุณมี flexibility เปลี่ยน implementation โดยไม่ rewrite tests ทั้งหมด ด้วย effort น้อยกว่า average test suite ต่อ object implementation

Amortize test costs ด้วย TDD

ส่วนแรก เรียนทดสอบทุกอย่าง ส่วนสอง เรียนทดสอบแค่ public interfaces TDD รวมเป้าหมายขัดแย้ง 2 นี้

ด้วย TDD ทดสอบ drive design และ implementation โดย process:

  1. เขียน failing test สมมติโค้ดที่ต้องมีอยู่แล้ว
  2. เขียน simplest implementation ผ่านทดสอบ
  3. Refactor ลบ duplication (หรือ expressive มากขึ้น)
  4. รันทดสอบอีกครั้ง (ให้ยังผ่าน)
  5. กลับขั้น 1

ทำตามขั้นตอนนี้ ทดสอบทุกอย่าง (ไม่มีโค้ดเขียนโดยไม่มี failing test) ขณะที่ทดสอบแค่ public interfaces (ไม่เขียนทดสอบใหม่หลัง refactor)

ไม่เคยง่ายอย่างนั้น แตยังมีวิธี test-drive แม้โค้ดซับซ้อนที่สุด

TDD side benefits:

  • Flexible, tested object model (arguably main benefit)
  • System testable by definition future tests ถูกกว่า
  • Amortize testing costs ทั่ว development estimates accurate
  • อยู่ใน flow ไม่ต้อง決策 what to do next

เริ่มยังไง?

เริ่มคือส่วนยาก! เข้ rhythm test-driven code ยากหยุด

ครั้งหน้าทำ feature ใหม่ ลอง follow TDD steps ข้างบน จะได้ close to 100% coverage ด้วยงานน้อยที่สุด foundation มั่นใจ และมั่นใจ test suite ปกป้องตอนเปลี่ยนโค้ดปีหน้า หรือวันนี้ requirements เปลี่ยนอีก

จบแล้วส่ง email บอกผมผล

ด้วย testing process ตรงไปตรงมา ใช้เวลาน้อย決策ง่ายและไล่ bugs มากขึ้น เวลาเขียนโค้ดแก้ปัญหา customers/business มากขึ้น