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

เขียนเทสต์ครอบคลุมสำหรับโค้ดที่ซับซ้อน: คู่มือแรกเริ่ม

โค้ดส่วนไหนของคุณที่ยังไม่ได้ทดสอบ? เป็นโค้ดที่จัดการกับสถานการณ์ซับซ้อนที่คุณควบคุมไม่ได้ไหม? เช่น threads, การรันคำสั่ง, git, networking, หรือ UI?

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

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

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

เปลี่ยนมุมมอง

สิ่งที่รบกวนที่สุดเกี่ยวกับเทสต์เหล่านี้คืออะไร? มันจะใช้เวลานานขึ้น 10 เท่าของสิ่งที่คุณคิดว่าควรจะเป็น หากคุณประมาณเวลาที่เทสต์ช่วยประหยัดได้เทียบกับเวลาที่ใช้เขียนเทสต์ มันดูแล้วไม่คุ้มค่าเลย

แต่มันไม่ได้เกี่ยวกับแค่เทสต์ครั้งนี้เท่านั้น มันเกี่ยวกับ เทสต์ทั้งหมดในอนาคตของคุณ

โค้ดที่ทดสอบดีที่สุดที่เคยเห็น มีโครงสร้างรองรับมากมาย ไม่ได้มีแค่โค้ดใน test/models เท่านั้น โค้ดที่ทดสอบได้ดีมากจะมี fakes, mocks, ชุด test fixtures ที่ดี, และตัวเลือกการตั้งค่าสำหรับเทสต์เฉพาะ

ทั้งหมดนี้ใช้เวลาในการเขียนและรวบรวม

แต่เมื่อคุณมีมันแล้ว รู้สึก ดีมาก คุณสามารถสร้างเทสต์ต่อเนื่องกัน ได้ความมั่นใจในโค้ด และมั่นใจในความเร็วที่คุณเคลื่อนไหวได้หลังจากการลงทุนที่ทำไว้

คุณสามารถ พึ่งพา งานที่คุณทำไว้แล้วได้

ดังนั้น ไม่ได้เป็นแค่การป้องกันบั๊กในโค้ดซับซ้อน แต่มันยังเกี่ยวกับการทำให้โค้ดในอนาคตง่ายต่อการทดสอบ ทีละชิ้นส่วน

ทำเป็น Integration Test (ตอนนี้)

บางครั้ง ไม่ได้เกี่ยวกับการเข้าใจคุณค่า - เข้าใจแล้ว แต่คุณติดอยู่ที่ไม่รู้จะเขียน unit test ขนาดเล็ก เร็ว ได้อย่างไร

จะรู้ยังไงว่าคุณรันคำสั่ง git ที่ถูกต้องใน deployment tool โดยไม่รัน git จริง? จะรับรองได้ยังไงว่าคุณส่ง header ที่ถูกต้องไปยัง remote server?

ถ้ามีเวลาพอ คุณสร้าง fake คุณภาพดีให้เทสต์พึ่งพาได้

แต่มันดูเยอะเกินไป มีอีกวิธีหนึ่งคือ แยกการทดสอบเป็น 2 ขั้นตอน: "ทดสอบโค้ด" และ "เขียน mock"

แค่เรียก server จริง แค่รันคำสั่งจริง ทำไม?

  • เริ่มต้นง่ายกว่ามาก คุณน่าจะทดสอบคำสั่งเหล่านี้ด้วยมืออยู่แล้ว ใส่ในคอนโซลหรือลองในเบราว์เซอร์ แค่นำมาใส่ในเทสต์ก็จบ
  • เมื่อเขียน mock หรือ fake จริง คุณสามารถใช้เทสต์เหล่านี้ตรวจสอบว่า mock ทำงานถูกต้อง ถ้าพฤติกรรมในโลกจริงและจาก fake เหมือนกัน mock ก็น่าจะดี!

แต่คุณไม่อาจเก็บเทสต์แบบนี้ไว้นานนัก:

  • มีปัญหาเหมือน integration tests ทุกประการ อาจช้า ต้องการอินเทอร์เน็ต จริงจัง อาจเปราะบางเพราะพึ่งพาสิ่งที่แอปไม่ได้สนใจ
  • บางอย่างทดสอบในโลกจริงไม่ได้ เช่น บังคับ error codes เมื่อไม่ควบคุม server อีกฝั่ง
  • อาจถูกบล็อกโดย server ที่พึ่งพา ทำให้เทสต์ (และแอป) พัง เคยเกิดขึ้นจริง และเป็นปัญหาใหญ่

ดังนั้น การเขียนเทสต์เป็น real-world integration test ไม่ใช่โซลูชันถาวรหรือระยะยาว แม้จะมีข้อเสีย แต่ก็ยังช่วยได้ และหลังจากแทนที่แล้ว คุณยังเก็บ integration test ไว้ใน suite แยกต่างหาก เพื่อตรวจสอบโค้ดกับความจริง ไม่ใช่แค่สมมติฐาน

โค้ดบางส่วนทดสอบยาก ใช้เวลาสร้างโครงสร้างให้เขียนเทสต์เชื่อถือได้เร็ว และมักดูไมคุ้มค่า

แต่มันจะเป็นแรงจูงใจมากขึ้น และเมื่อเทสต์แรกผ่าน ที่เหลือจะดูง่ายขึ้นอย่างลึกลับ

บางครั้งก็ยังไม่พอ ถ้าคุณรู้ว่าโค้ดทำงานในโลกจริง แต่หาวิธีทดสอบไม่ได้?

เมื่อเกิดเหตุการณ์นี้ หยุดมองเทสต์ว่าต้องบริสุทธิ์และแยกต่างหาก มองมันเป็นวิธีทำให้สิ่งที่คุณทำด้วยมือกลายเป็นอัตโนมัติ

มันไม่สมบูรณ์ และควรแทนที่เร็วที่สุด แต่เทสต์เหล่านี้ให้ความมั่นใจที่ต้องการเพื่อเขียนและเปลี่ยนแปลงโค้ดซับซ้อนอย่างรวดเร็ว