portaldacalheta.pt
  • หลัก
  • การจัดการวิศวกรรม
  • Kpi และ Analytics
  • เทคโนโลยี
  • ว่องไว
แบ็คเอนด์

การทดสอบหน่วยวิธีการเขียนโค้ดที่ทดสอบได้และเหตุใดจึงสำคัญ



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

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



แบบฝึกหัดการทดสอบหน่วย: ภาพประกอบหน้าปก



การทดสอบหน่วยคืออะไร?

โดยพื้นฐานแล้วการทดสอบหน่วยเป็นวิธีการที่สร้างอินสแตนซ์ส่วนเล็ก ๆ ของแอปพลิเคชันของเราและตรวจสอบพฤติกรรมของแอปพลิเคชัน เป็นอิสระจากส่วนอื่น ๆ . การทดสอบหน่วยโดยทั่วไปประกอบด้วย 3 ขั้นตอนขั้นแรกจะเริ่มต้นแอปพลิเคชันชิ้นเล็ก ๆ ที่ต้องการทดสอบ (หรือที่เรียกว่าระบบที่อยู่ระหว่างการทดสอบหรือมทส.) จากนั้นจะใช้สิ่งกระตุ้นบางอย่างกับระบบที่กำลังทดสอบ (โดยปกติจะเรียก วิธีการ) และในที่สุดก็สังเกตพฤติกรรมที่เกิดขึ้น หากพฤติกรรมที่สังเกตได้สอดคล้องกับความคาดหวังการทดสอบหน่วยจะผ่านไปมิฉะนั้นจะล้มเหลวแสดงว่ามีปัญหาในระบบที่อยู่ระหว่างการทดสอบ ขั้นตอนการทดสอบหน่วยทั้งสามนี้เรียกอีกอย่างว่าการจัดเรียงการกระทำและการยืนยันหรือเรียกง่ายๆว่า AAA



การทดสอบหน่วยสามารถตรวจสอบพฤติกรรมที่แตกต่างกันของระบบที่อยู่ระหว่างการทดสอบ แต่ส่วนใหญ่แล้วการทดสอบจะจัดอยู่ในประเภทใดประเภทหนึ่งจากสองประเภทต่อไปนี้: ตามรัฐ หรือ ตามปฏิสัมพันธ์ . การตรวจสอบว่าระบบภายใต้การทดสอบให้ผลลัพธ์ที่ถูกต้องหรือสถานะผลลัพธ์ถูกต้องเรียกว่า ตามรัฐ การทดสอบหน่วยในขณะที่ตรวจสอบว่ามีการเรียกใช้วิธีการบางอย่างอย่างถูกต้อง ตามปฏิสัมพันธ์ การทดสอบหน่วย

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



การทดสอบหน่วยคืออะไร: ภาพประกอบ

ฉันจะใช้ C # สำหรับตัวอย่างทั้งหมดในบทความนี้ แต่แนวคิดที่อธิบายนั้นใช้กับภาษาโปรแกรมเชิงวัตถุทั้งหมด



ตัวอย่างการทดสอบหน่วยง่ายๆอาจมีลักษณะดังนี้

[TestMethod] public void IsPalindrome_ForPalindromeString_ReturnsTrue() { // In the Arrange phase, we create and set up a system under test. // A system under test could be a method, a single object, or a graph of connected objects. // It is OK to have an empty Arrange phase, for example if we are testing a static method - // in this case SUT already exists in a static form and we don't have to initialize anything explicitly. PalindromeDetector detector = new PalindromeDetector(); // The Act phase is where we poke the system under test, usually by invoking a method. // If this method returns something back to us, we want to collect the result to ensure it was correct. // Or, if method doesn't return anything, we want to check whether it produced the expected side effects. bool isPalindrome = detector.IsPalindrome('kayak'); // The Assert phase makes our unit test pass or fail. // Here we check that the method's behavior is consistent with expectations. Assert.IsTrue(isPalindrome); }

การทดสอบหน่วยเทียบกับการทดสอบการบูรณาการ

สิ่งสำคัญอีกอย่างที่ต้องพิจารณาคือความแตกต่างระหว่างการทดสอบหน่วยและการทดสอบการรวม



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

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



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

ภาพประกอบตัวอย่างการทดสอบหน่วย



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

แนวทางปฏิบัติที่ดีที่สุดในการสร้างแบบจำลองทางการเงินของ excel

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

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

การทดสอบหน่วยที่ดีคืออะไร?

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

  • เขียนง่าย. โดยทั่วไปนักพัฒนาจะเขียนการทดสอบหน่วยจำนวนมากเพื่อให้ครอบคลุมกรณีและแง่มุมต่างๆของพฤติกรรมของแอปพลิเคชันดังนั้นจึงควรเขียนโค้ดขั้นตอนการทดสอบทั้งหมดได้อย่างง่ายดายโดยไม่ต้องออกแรงมาก

  • อ่านได้. เจตนาของการทดสอบหน่วยควรชัดเจน การทดสอบหน่วยที่ดีจะบอกเล่าเรื่องราวเกี่ยวกับลักษณะพฤติกรรมบางอย่างของแอปพลิเคชันของเราดังนั้นจึงควรทำความเข้าใจได้ง่ายว่าสถานการณ์ใดกำลังถูกทดสอบและ - หากการทดสอบล้มเหลว - ง่ายต่อการตรวจจับวิธีแก้ไขปัญหา ด้วยการทดสอบหน่วยที่ดีเราสามารถแก้ไขข้อบกพร่องโดยไม่ต้องแก้ไขข้อบกพร่องของโค้ดจริงๆ!

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

  • เร็ว. นักพัฒนาเขียนการทดสอบหน่วยเพื่อให้สามารถเรียกใช้ซ้ำได้และตรวจสอบว่าไม่มีข้อบกพร่องใด ๆ หากการทดสอบหน่วยทำงานช้านักพัฒนามีแนวโน้มที่จะข้ามการรันบนเครื่องของตนเอง การทดสอบอย่างช้าๆเพียงครั้งเดียวจะไม่สร้างความแตกต่างอย่างมีนัยสำคัญ เพิ่มอีกหนึ่งพันและเราคงต้องรออีกสักพัก การทดสอบหน่วยที่ช้าอาจบ่งชี้ว่าระบบที่อยู่ระหว่างการทดสอบหรือการทดสอบเองนั้นโต้ตอบกับระบบภายนอกทำให้ขึ้นอยู่กับสภาพแวดล้อม

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

เท่านี้ก็ไม่มีความลับในการเขียน การทดสอบหน่วย . อย่างไรก็ตามมีเทคนิคบางอย่างที่ช่วยให้เราสามารถเขียนได้ รหัสที่ทดสอบได้ .

รหัสที่ทดสอบได้และไม่สามารถทดสอบได้

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

การทำให้ Codebase เป็นพิษด้วยปัจจัยที่ไม่ได้กำหนด

เริ่มจากตัวอย่างง่ายๆ ลองนึกภาพว่าเรากำลังเขียนโปรแกรมสำหรับไมโครคอนโทรลเลอร์สำหรับบ้านอัจฉริยะและข้อกำหนดประการหนึ่งคือการเปิดไฟในสนามหลังบ้านโดยอัตโนมัติหากตรวจพบการเคลื่อนไหวบางอย่างในตอนเย็นหรือตอนกลางคืน เราได้เริ่มต้นจากด้านล่างขึ้นบนโดยใช้วิธีที่ส่งกลับการแสดงสตริงของเวลาโดยประมาณของวัน (“ กลางคืน”“ เช้า”“ ตอนบ่าย” หรือ“ ตอนเย็น”):

adobe xd cc . คืออะไร
public static string GetTimeOfDay() { DateTime time = DateTime.Now; if (time.Hour >= 0 && time.Hour = 6 && time.Hour = 12 && time.Hour <18) { return 'Afternoon'; } return 'Evening'; }

โดยพื้นฐานแล้ววิธีนี้จะอ่านเวลาของระบบปัจจุบันและส่งคืนผลลัพธ์ตามค่านั้น แล้วโค้ดนี้ผิดอะไร

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

ดังกล่าว ไม่ได้กำหนด พฤติกรรมทำให้ไม่สามารถทดสอบตรรกะภายในของ GetTimeOfDay() วิธีการโดยไม่ต้องเปลี่ยนวันที่และเวลาของระบบ มาดูกันว่าจะต้องดำเนินการทดสอบดังกล่าวอย่างไร:

[TestMethod] public void GetTimeOfDay_At6AM_ReturnsMorning() { try { // Setup: change system time to 6 AM ... // Arrange phase is empty: testing static method, nothing to initialize // Act string timeOfDay = GetTimeOfDay(); // Assert Assert.AreEqual('Morning', timeOfDay); } finally { // Teardown: roll system time back ... } }

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

ปรากฎว่าปัญหาการทดสอบเหล่านี้เกิดจาก GetTimeOfDay() คุณภาพต่ำ API ในรูปแบบปัจจุบันวิธีนี้ประสบปัญหาหลายประการ:

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

  • มันละเมิด หลักการความรับผิดชอบเดียว (เคียว). วิธีนี้มีความรับผิดชอบหลายประการ มันใช้ข้อมูลและประมวลผลด้วย ตัวบ่งชี้อีกประการหนึ่งของการละเมิด SRP คือเมื่อคลาสเดียวหรือเมธอดมีมากกว่าหนึ่ง เหตุผลในการเปลี่ยนแปลง . จากมุมมองนี้ GetTimeOfDay() วิธีการอาจเปลี่ยนแปลงได้เนื่องจากการปรับตรรกะภายในหรือเนื่องจากควรเปลี่ยนแหล่งวันที่และเวลา

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

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

หลังจากตรวจสอบ API แล้วเรามาแก้ไขกันในที่สุด! โชคดีที่สิ่งนี้ง่ายกว่าการพูดถึงข้อบกพร่องทั้งหมด - เราเพียงแค่ต้องทำลายข้อกังวลที่เกี่ยวข้องกันอย่างแน่นหนา

การแก้ไข API: แนะนำวิธีการโต้แย้ง

วิธีที่ง่ายและชัดเจนที่สุดในการแก้ไข API คือการแนะนำวิธีการโต้แย้ง:

public static string GetTimeOfDay(DateTime dateTime) { if (dateTime.Hour >= 0 && dateTime.Hour = 6 && dateTime.Hour = 12 && dateTime.Hour <18) { return 'Noon'; } return 'Evening'; }

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

[TestMethod] public void GetTimeOfDay_For6AM_ReturnsMorning() { // Arrange phase is empty: testing static method, nothing to initialize // Act string timeOfDay = GetTimeOfDay(new DateTime(2015, 12, 31, 06, 00, 00)); // Assert Assert.AreEqual('Morning', timeOfDay); }

โปรดสังเกตว่า refactor แบบธรรมดานี้ยังแก้ไขปัญหา API ทั้งหมดที่กล่าวถึงก่อนหน้านี้ (การมีเพศสัมพันธ์แบบแน่นการละเมิด SRP API ที่ไม่ชัดเจนและยากที่จะเข้าใจ) โดยการแนะนำรอยต่อที่ชัดเจนระหว่าง อะไร ข้อมูลควรได้รับการประมวลผลและ อย่างไร มันควรจะทำ

ยอดเยี่ยม - วิธีนี้สามารถทดสอบได้ แต่วิธีการนั้น ลูกค้า เหรอ? ตอนนี้มันเป็น ผู้โทร รับผิดชอบในการระบุวันที่และเวลาให้กับ GetTimeOfDay(DateTime dateTime) วิธีการหมายความว่า พวกเขา อาจไม่สามารถพิสูจน์ได้หากเราไม่ใส่ใจมากพอ มาดูกันว่าเราจะจัดการกับสิ่งนั้นได้อย่างไร

การแก้ไข Client API: Dependency Injection

สมมติว่าเราทำงานในระบบบ้านอัจฉริยะต่อไปและใช้ไคลเอนต์ต่อไปนี้ของ GetTimeOfDay(DateTime dateTime) วิธีการ - รหัสไมโครคอนโทรลเลอร์สมาร์ทโฮมดังกล่าวข้างต้นรับผิดชอบในการเปิดหรือปิดไฟตามช่วงเวลาของวันและการตรวจจับการเคลื่อนไหว:

public class SmartHomeController { public DateTime LastMotionTime { get; private set; } public void ActuateLights(bool motionDetected) { DateTime time = DateTime.Now; // Ouch! // Update the time of last motion. if (motionDetected) { LastMotionTime = time; } // If motion was detected in the evening or at night, turn the light on. string timeOfDay = GetTimeOfDay(time); if (motionDetected && (timeOfDay == 'Evening' || timeOfDay == 'Night')) { BackyardLightSwitcher.Instance.TurnOn(); } // If no motion is detected for one minute, or if it is morning or day, turn the light off. else if (time.Subtract(LastMotionTime) > TimeSpan.FromMinutes(1) || (timeOfDay == 'Morning' || timeOfDay == 'Noon')) { BackyardLightSwitcher.Instance.TurnOff(); } } }

อุ๊ย! เรามีแบบเดียวกันซ่อนอยู่ DateTime.Now ปัญหาการป้อนข้อมูล - ข้อแตกต่างเพียงอย่างเดียวคือมันอยู่ในระดับนามธรรมที่สูงขึ้นเล็กน้อย ในการแก้ปัญหานี้เราสามารถแนะนำข้อโต้แย้งอื่นอีกครั้งโดยมอบหมายความรับผิดชอบในการให้ DateTime มูลค่าให้กับผู้เรียกใช้วิธีการใหม่พร้อมลายเซ็น ActuateLights(bool motionDetected, DateTime dateTime) แต่แทนที่จะย้ายปัญหาในระดับที่สูงขึ้นใน call stack อีกครั้งลองใช้เทคนิคอื่นที่จะช่วยให้เรารักษาทั้งสองอย่าง ActuateLights(bool motionDetected) วิธีการและลูกค้าทดสอบได้: การผกผันของการควบคุม หรือ IoC.

Inversion of Control เป็นเทคนิคที่เรียบง่าย แต่มีประโยชน์อย่างยิ่งสำหรับการแยกรหัสและสำหรับการทดสอบหน่วยโดยเฉพาะ (ท้ายที่สุดแล้วการทำให้สิ่งต่าง ๆ อยู่คู่กันอย่างหลวม ๆ เป็นสิ่งจำเป็นสำหรับการวิเคราะห์สิ่งเหล่านี้อย่างอิสระจากกัน) ประเด็นสำคัญของ IoC คือการแยกรหัสการตัดสินใจ ( เมื่อไหร่ ทำอะไรบางอย่าง) จากรหัสการกระทำ ( อะไร จะทำเมื่อมีอะไรเกิดขึ้น) เทคนิคนี้เพิ่มความยืดหยุ่นทำให้โค้ดของเราเป็นโมดูลาร์มากขึ้นและลดการเชื่อมต่อระหว่างส่วนประกอบต่างๆ

การควบคุมแบบผกผันสามารถดำเนินการได้หลายวิธี เรามาดูตัวอย่างกัน - การฉีดพึ่งพา การใช้ตัวสร้าง - และจะช่วยในการสร้างแบบทดสอบได้อย่างไร SmartHomeController API

ก่อนอื่นมาสร้าง IDateTimeProvider อินเทอร์เฟซที่มีลายเซ็นวิธีการสำหรับการรับวันที่และเวลา:

public interface IDateTimeProvider { DateTime GetDateTime(); }

จากนั้นสร้าง SmartHomeController อ้างอิง IDateTimeProvider การดำเนินการและมอบหมายความรับผิดชอบในการรับวันที่และเวลา:

public class SmartHomeController { private readonly IDateTimeProvider _dateTimeProvider; // Dependency public SmartHomeController(IDateTimeProvider dateTimeProvider) { // Inject required dependency in the constructor. _dateTimeProvider = dateTimeProvider; } public void ActuateLights(bool motionDetected) { DateTime time = _dateTimeProvider.GetDateTime(); // Delegating the responsibility // Remaining light control logic goes here... } }

ตอนนี้เราสามารถดูได้ว่าทำไม Inversion of Control จึงเรียกว่า: ควบคุม กลไกที่ใช้ในการอ่านวันที่และเวลาคืออะไร กลับหัว และตอนนี้เป็นของไฟล์ ลูกค้า ของ SmartHomeController ไม่ใช่ SmartHomeController ตัวเอง ดังนั้นการดำเนินการของ ActuateLights(bool motionDetected) วิธีการขึ้นอยู่กับสองสิ่งที่สามารถจัดการได้ง่ายจากภายนอก: motionDetected อาร์กิวเมนต์และการนำไปใช้อย่างเป็นรูปธรรมของ IDateTimeProvider ส่งผ่านไปยัง SmartHomeController ผู้สร้าง

เหตุใดสิ่งนี้จึงสำคัญสำหรับการทดสอบหน่วย มันหมายความว่า IDateTimeProvider การใช้งานสามารถใช้ในรหัสการผลิตและรหัสทดสอบหน่วย ในสภาพแวดล้อมการผลิตการใช้งานจริงบางอย่างจะถูกฉีดเข้าไป (เช่นอันที่อ่านเวลาระบบจริง) อย่างไรก็ตามในการทดสอบหน่วยเราสามารถฉีดการใช้งาน 'ปลอม' ที่ส่งคืนค่าคงที่หรือที่กำหนดไว้ล่วงหน้า DateTime ค่าที่เหมาะสมสำหรับการทดสอบสถานการณ์เฉพาะ

การติดตั้ง IDateTimeProvider อาจมีลักษณะดังนี้:

public class FakeDateTimeProvider : IDateTimeProvider { public DateTime ReturnValue { get; set; } public DateTime GetDateTime() { return ReturnValue; } public FakeDateTimeProvider(DateTime returnValue) { ReturnValue = returnValue; } }

ด้วยความช่วยเหลือของคลาสนี้คุณสามารถแยก SmartHomeController จากปัจจัยที่ไม่ได้กำหนดและทำการทดสอบหน่วยตามรัฐ เรามาตรวจสอบกันว่าหากตรวจพบการเคลื่อนไหวเวลาของการเคลื่อนไหวนั้นจะถูกบันทึกไว้ใน LastMotionTime คุณสมบัติ:

[TestMethod] void ActuateLights_MotionDetected_SavesTimeOfMotion() { // Arrange var controller = new SmartHomeController(new FakeDateTimeProvider(new DateTime(2015, 12, 31, 23, 59, 59))); // Act controller.ActuateLights(true); // Assert Assert.AreEqual(new DateTime(2015, 12, 31, 23, 59, 59), controller.LastMotionTime); }

เยี่ยมมาก! การทดสอบเช่นนี้ไม่สามารถทำได้ก่อนการปรับโครงสร้างใหม่ ตอนนี้เราได้กำจัดปัจจัยที่ไม่ใช่ปัจจัยกำหนดและยืนยันสถานการณ์ตามรัฐแล้วคุณคิดว่า SmartHomeController สามารถทดสอบได้อย่างสมบูรณ์?

การวางยาพิษ Codebase ด้วยผลข้างเคียง

แม้ว่าเราจะแก้ไขปัญหาที่เกิดจากอินพุตที่ซ่อนอยู่ที่ไม่ได้กำหนดปัจจัยและเราสามารถทดสอบฟังก์ชันการทำงานบางอย่างได้ แต่โค้ด (หรืออย่างน้อยก็บางส่วน) ก็ยังไม่สามารถทดสอบได้!

มาดูส่วนต่อไปนี้ของ ActuateLights(bool motionDetected) วิธีการที่รับผิดชอบในการเปิดหรือปิดไฟ:

// If motion was detected in the evening or at night, turn the light on. if (motionDetected && (timeOfDay == 'Evening' || timeOfDay == 'Night')) { BackyardLightSwitcher.Instance.TurnOn(); } // If no motion was detected for one minute, or if it is morning or day, turn the light off. else if (time.Subtract(LastMotionTime) > TimeSpan.FromMinutes(1) || (timeOfDay == 'Morning' || timeOfDay == 'Noon')) { BackyardLightSwitcher.Instance.TurnOff(); }

อย่างที่เราเห็น SmartHomeController มอบหมายความรับผิดชอบในการเปิดหรือปิดไฟเป็น BackyardLightSwitcher วัตถุซึ่งใช้ a รูปแบบ Singleton . การออกแบบนี้มีอะไรผิดปกติ

เพื่อทดสอบหน่วยการเรียนรู้ ActuateLights(bool motionDetected) วิธีการเราควรทำการทดสอบตามปฏิสัมพันธ์นอกเหนือจากการทดสอบตามรัฐ นั่นคือเราควรตรวจสอบให้แน่ใจว่ามีการเรียกวิธีการเปิดหรือปิดไฟถ้าตรงตามเงื่อนไขที่เหมาะสมเท่านั้น น่าเสียดายที่การออกแบบในปัจจุบันไม่อนุญาตให้เราทำเช่นนั้น: _ + _ | และ TurnOn() วิธีการของ TurnOff() ทำให้เกิดการเปลี่ยนแปลงสถานะบางอย่างในระบบหรือกล่าวอีกนัยหนึ่งคือสร้าง ผลข้างเคียง . วิธีเดียวที่จะตรวจสอบว่ามีการเรียกวิธีการเหล่านี้คือการตรวจสอบว่าผลข้างเคียงเกิดขึ้นจริงหรือไม่ซึ่งอาจเจ็บปวด

สมมติว่าเซ็นเซอร์ตรวจจับความเคลื่อนไหวโคมไฟสนามหลังบ้านและไมโครคอนโทรลเลอร์ในบ้านอัจฉริยะเชื่อมต่อกับเครือข่าย Internet of Things และสื่อสารโดยใช้โปรโตคอลไร้สายบางอย่าง ในกรณีนี้การทดสอบหน่วยอาจพยายามรับและวิเคราะห์การรับส่งข้อมูลเครือข่ายนั้น หรือหากส่วนประกอบฮาร์ดแวร์เชื่อมต่อด้วยสายไฟการทดสอบหน่วยสามารถตรวจสอบว่าแรงดันไฟฟ้าถูกนำไปใช้กับวงจรไฟฟ้าที่เหมาะสมหรือไม่ หรือหลังจากนั้นก็สามารถตรวจสอบว่าไฟเปิดหรือปิดจริงโดยใช้เซ็นเซอร์วัดแสงเพิ่มเติม

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

อีกครั้งปัญหาการทดสอบทั้งหมดนี้เกิดจาก API ที่ไม่ดีไม่ใช่ความสามารถของนักพัฒนาในการเขียนการทดสอบหน่วย ไม่ว่าจะใช้การควบคุมแสงอย่างไรก็ตาม BackyardLightSwitcher API ประสบปัญหาที่คุ้นเคยอยู่แล้วเหล่านี้:

  • ควบคู่ไปกับการดำเนินการอย่างเป็นรูปธรรม API อาศัยอินสแตนซ์แบบฮาร์ดโค้ดที่เป็นรูปธรรมของ SmartHomeController ไม่สามารถนำ BackyardLightSwitcher กลับมาใช้ใหม่ได้ วิธีเปลี่ยนไฟอื่น ๆ นอกเหนือจากที่อยู่ในสนามหลังบ้าน

  • เป็นการละเมิดหลักการความรับผิดชอบเดียว API มีเหตุผลสองประการในการเปลี่ยนแปลง: ประการแรกการเปลี่ยนแปลงตรรกะภายใน (เช่นการเลือกให้ไฟเปิดเฉพาะตอนกลางคืน แต่ไม่ใช่ในตอนเย็น) และประการที่สองหากกลไกการสลับแสงถูกแทนที่ด้วยกลไกอื่น

  • มันขึ้นอยู่กับการอ้างอิง ไม่มีทางที่นักพัฒนาจะรู้ว่า ActuateLights(bool motionDetected) ขึ้นอยู่กับฮาร์ดโค้ด SmartHomeController ส่วนประกอบนอกเหนือจากการขุดลงในซอร์สโค้ด

  • เป็นเรื่องยากที่จะเข้าใจและรักษา จะเกิดอะไรขึ้นถ้าไฟไม่ยอมเปิดเมื่อเงื่อนไขถูกต้อง? เราสามารถใช้เวลามากในการพยายามแก้ไข BackyardLightSwitcher ไม่เป็นประโยชน์เพียง แต่ต้องตระหนักว่าปัญหาเกิดจากข้อบกพร่องใน SmartHomeController (หรือยิ่งกว่านั้นคือหลอดไฟที่ถูกเผาไหม้!)

การแก้ปัญหาของทั้งความสามารถในการทดสอบและ API คุณภาพต่ำนั้นไม่น่าแปลกใจที่จะแยกส่วนประกอบที่เชื่อมต่อกันอย่างแน่นหนาออกจากกัน เช่นเดียวกับตัวอย่างก่อนหน้านี้การใช้ Dependency Injection จะช่วยแก้ปัญหาเหล่านี้ได้ เพียงแค่เพิ่ม BackyardLightSwitcher การพึ่งพา ILightSwitcher มอบความรับผิดชอบในการพลิกสวิตช์ไฟและส่งของปลอมทดสอบเท่านั้น SmartHomeController การดำเนินการที่จะบันทึกว่ามีการเรียกใช้วิธีการที่เหมาะสมภายใต้เงื่อนไขที่เหมาะสมหรือไม่ อย่างไรก็ตามแทนที่จะใช้ Dependency Injection อีกครั้งลองทบทวนแนวทางอื่นที่น่าสนใจสำหรับการแยกส่วนความรับผิดชอบ

แก้ไข API: ฟังก์ชันลำดับที่สูงขึ้น

แนวทางนี้เป็นตัวเลือกในภาษาเชิงวัตถุใด ๆ ที่รองรับ ฟังก์ชั่นชั้นหนึ่ง . มาใช้ประโยชน์จากคุณสมบัติการทำงานของ C # และสร้าง ILightSwitcher วิธีการยอมรับอีกสองอาร์กิวเมนต์: คู่ของ ActuateLights(bool motionDetected) ผู้ได้รับมอบหมายชี้ไปที่วิธีการที่ควรเรียกเพื่อเปิดและปิดไฟ โซลูชันนี้จะแปลงวิธีการเป็นไฟล์ ฟังก์ชันลำดับที่สูงขึ้น :

วิธีทำมอนติคาร์โลจำลอง
Action

นี่เป็นโซลูชันที่มีรสชาติที่ใช้งานได้ดีกว่าวิธี Dependency Injection แบบเชิงวัตถุแบบคลาสสิกที่เราเคยเห็นมาก่อน อย่างไรก็ตามมันช่วยให้เราได้ผลลัพธ์เดียวกันโดยใช้รหัสน้อยลงและแสดงออกได้มากกว่า Dependency Injection ไม่จำเป็นต้องใช้คลาสที่สอดคล้องกับอินเทอร์เฟซเพื่อจัดหา public void ActuateLights(bool motionDetected, Action turnOn, Action turnOff) { DateTime time = _dateTimeProvider.GetDateTime(); // Update the time of last motion. if (motionDetected) { LastMotionTime = time; } // If motion was detected in the evening or at night, turn the light on. string timeOfDay = GetTimeOfDay(time); if (motionDetected && (timeOfDay == 'Evening' || timeOfDay == 'Night')) { turnOn(); // Invoking a delegate: no tight coupling anymore } // If no motion is detected for one minute, or if it is morning or day, turn the light off. else if (time.Subtract(LastMotionTime) > TimeSpan.FromMinutes(1) || (timeOfDay == 'Morning' || timeOfDay == 'Noon')) { turnOff(); // Invoking a delegate: no tight coupling anymore } } อีกต่อไป ด้วยฟังก์ชันที่จำเป็น เราสามารถส่งนิยามฟังก์ชันแทนได้ ฟังก์ชันลำดับที่สูงกว่าสามารถคิดได้ว่าเป็นอีกวิธีหนึ่งในการนำ Inversion of Control ไปใช้

ตอนนี้เพื่อทำการทดสอบหน่วยตามปฏิสัมพันธ์ของวิธีผลลัพธ์เราสามารถส่งต่อการกระทำปลอมที่ตรวจสอบได้อย่างง่ายดาย:

SmartHomeController

ในที่สุดเราก็ได้สร้าง [TestMethod] public void ActuateLights_MotionDetectedAtNight_TurnsOnTheLight() { // Arrange: create a pair of actions that change boolean variable instead of really turning the light on or off. bool turnedOn = false; Action turnOn = () => turnedOn = true; Action turnOff = () => turnedOn = false; var controller = new SmartHomeController(new FakeDateTimeProvider(new DateTime(2015, 12, 31, 23, 59, 59))); // Act controller.ActuateLights(true, turnOn, turnOff); // Assert Assert.IsTrue(turnedOn); } API สามารถทดสอบได้อย่างสมบูรณ์และเราสามารถทำการทดสอบทั้งแบบอิงตามสถานะและแบบโต้ตอบได้ โปรดสังเกตอีกครั้งว่านอกเหนือจากความสามารถในการทดสอบที่ดีขึ้นแล้วการแนะนำรอยต่อระหว่างรหัสการตัดสินใจและรหัสการดำเนินการยังช่วยแก้ปัญหาการมีเพศสัมพันธ์ที่แน่นหนาและนำไปสู่ ​​API ที่สะอาดและนำกลับมาใช้ใหม่ได้

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

ความไม่บริสุทธิ์และความสามารถในการทดสอบ

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

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

ความไม่บริสุทธิ์เป็นพิษ: if method SmartHomeController ขึ้นอยู่กับวิธีการที่ไม่กำหนดหรือผลข้างเคียง Foo() แล้ว Bar() กลายเป็นสิ่งที่ไม่กำหนดหรือผลข้างเคียงเช่นกัน ในที่สุดเราอาจจะวางยาพิษทั้ง codebase คูณปัญหาเหล่านี้ทั้งหมดตามขนาดของแอปพลิเคชันในชีวิตจริงที่ซับซ้อนและเราจะพบว่าตัวเองมีภาระผูกพันกับโค้ดเบสที่ยากต่อการดูแลรักษาซึ่งเต็มไปด้วยกลิ่นรูปแบบการต่อต้านการอ้างอิงที่เป็นความลับและสิ่งที่น่าเกลียดและไม่พึงประสงค์ทุกประเภท

ตัวอย่างการทดสอบหน่วย: ภาพประกอบ

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

สัญญาณเตือนทั่วไปของรหัสทดสอบยาก

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

สุดท้ายนี้เรามาดูสัญญาณเตือนทั่วไปที่บ่งชี้ว่าโค้ดของเราอาจทดสอบได้ยาก

คุณสมบัติคงที่และฟิลด์

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

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

Foo()

จะเกิดอะไรขึ้นถ้า if (!SmartHomeSettings.CostSavingEnabled) { _swimmingPoolController.HeatWater(); } วิธีการไม่ถูกเรียกเมื่อเราแน่ใจว่าควรได้รับ? เนื่องจากส่วนใดส่วนหนึ่งของแอปพลิเคชันอาจมีการเปลี่ยนแปลง HeatWater() ค่าเราต้องหาและวิเคราะห์สถานที่ทั้งหมดที่แก้ไขค่านั้นเพื่อค้นหาว่ามีอะไรผิดปกติ นอกจากนี้ตามที่เราได้เห็นไปแล้วยังไม่สามารถตั้งค่าคุณสมบัติคงที่สำหรับวัตถุประสงค์ในการทดสอบได้ (เช่น CostSavingEnabled หรือ DateTime.Now ซึ่งเป็นแบบอ่านอย่างเดียว แต่ยังไม่สามารถกำหนดได้)

ในทางกลับกันไม่เปลี่ยนรูป และ สภาวะโลกที่กำหนดได้นั้นตกลงโดยสิ้นเชิง อันที่จริงมีชื่อที่คุ้นเคยมากกว่านี้ - ค่าคงที่ ค่าคงที่เช่น Environment.MachineName ห้ามนำสิ่งที่ไม่ใช่ปัจจัยกำหนดใด ๆ และเนื่องจากค่าของพวกเขาไม่สามารถเปลี่ยนแปลงได้จึงไม่อนุญาตให้มีผลข้างเคียงใด ๆ :

Math.PI

เสื้อกล้าม

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

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

double Circumference(double radius) { return 2 * Math.PI * radius; } // Still a pure function!

ในตัวอย่างข้างต้นหากการทดสอบสำหรับสถานการณ์การโจมตีแคชทำงานก่อนจะเป็นการเพิ่มผู้ใช้ใหม่ลงในแคชดังนั้นการทดสอบสถานการณ์การพลาดแคชในภายหลังอาจล้มเหลวเนื่องจากถือว่าแคชว่างเปล่า เพื่อเอาชนะสิ่งนี้เราจะต้องเขียนโค้ดการฉีกขาดเพิ่มเติมเพื่อล้าง User GetUser(int userId) { User user; if (UserCache.Instance.ContainsKey(userId)) { user = UserCache.Instance[userId]; } else { user = _userService.LoadUser(userId); UserCache.Instance[userId] = user; } return user; } หลังจากการทดสอบแต่ละหน่วย

การใช้ Singletons เป็นแนวทางปฏิบัติที่ไม่ดีที่สามารถ (และควร) หลีกเลี่ยงได้ในกรณีส่วนใหญ่ อย่างไรก็ตามสิ่งสำคัญคือต้องแยกความแตกต่างระหว่าง Singleton เป็นรูปแบบการออกแบบและอินสแตนซ์เดียวของวัตถุ ในกรณีหลังนี้ความรับผิดชอบในการสร้างและดูแลอินสแตนซ์เดียวจะขึ้นอยู่กับแอปพลิเคชันเอง โดยทั่วไปแล้วจะส่งมอบด้วยโรงงานหรือคอนเทนเนอร์ Dependency Injection ซึ่งจะสร้างอินสแตนซ์เดียวที่อยู่ใกล้กับ“ ด้านบน” ของแอปพลิเคชัน (เช่นอยู่ใกล้กับจุดเริ่มต้นของแอปพลิเคชันมากขึ้น) จากนั้นส่งผ่านไปยังทุกวัตถุที่ต้องการ แนวทางนี้ถูกต้องอย่างยิ่งทั้งจากความสามารถในการทดสอบและมุมมองด้านคุณภาพของ API

UserCache ตัวดำเนินการ

การสร้างอินสแตนซ์ของออบเจ็กต์ใหม่เพื่อให้งานบางอย่างเสร็จสิ้นทำให้เกิดปัญหาเดียวกันกับรูปแบบการต่อต้าน Singleton: API ที่ไม่ชัดเจนพร้อมการอ้างอิงที่ซ่อนอยู่การมีเพศสัมพันธ์ที่แน่นหนาและการทดสอบที่ไม่ดี

ตัวอย่างเช่นในการทดสอบว่าลูปต่อไปนี้หยุดหรือไม่เมื่อส่งคืนรหัสสถานะ 404 นักพัฒนาควรตั้งค่าเว็บเซิร์ฟเวอร์ทดสอบ:

new

อย่างไรก็ตามบางครั้ง using (var client = new HttpClient()) { HttpResponseMessage response; do { response = await client.GetAsync(uri); // Process the response and update the uri... } while (response.StatusCode != HttpStatusCode.NotFound); } ไม่เป็นอันตรายอย่างแน่นอน: ตัวอย่างเช่นสามารถสร้างเอนทิตีออบเจ็กต์อย่างง่ายได้:

new

นอกจากนี้ยังสามารถสร้างวัตถุชั่วคราวขนาดเล็กที่ไม่ก่อให้เกิดผลข้างเคียงใด ๆ ยกเว้นการแก้ไขสถานะของตนเองแล้วส่งคืนผลลัพธ์ตามสถานะนั้น ในตัวอย่างต่อไปนี้เราไม่สนใจว่า var person = new Person('John', 'Doe', new DateTime(1970, 12, 31)); มีการเรียกวิธีการหรือไม่ - เราเพียงตรวจสอบว่าผลลัพธ์สุดท้ายถูกต้องหรือไม่:

Stack

วิธีการแบบคงที่

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

aws โซลูชั่น สถาปนิก สอบสมทบ

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

string ReverseString(string input) { // No need to do interaction-based testing and check that Stack methods were called or not; // The unit test just needs to ensure that the return value is correct (state-based testing). var stack = new Stack(); foreach(var s in input) { stack.Push(s); } string result = string.Empty; while(stack.Count != 0) { result += stack.Pop(); } return result; }

อย่างไรก็ตาม บริสุทธิ์ ฟังก์ชันคงที่ใช้ได้: การรวมกันใด ๆ จะยังคงเป็นฟังก์ชันบริสุทธิ์ ตัวอย่างเช่น:

void CheckPathEnvironmentVariable() { if (Environment.GetEnvironmentVariable('PATH') != null) { Console.WriteLine('PATH environment variable exists.'); } else { Console.WriteLine('PATH environment variable is not defined.'); } }

ประโยชน์ของการทดสอบหน่วย

เห็นได้ชัดว่าการเขียนโค้ดที่สามารถทดสอบได้นั้นต้องมีวินัยสมาธิและความพยายามเป็นพิเศษ แต่การพัฒนาซอฟต์แวร์ก็เป็นกิจกรรมทางจิตใจที่ซับซ้อนและเราควรระมัดระวังอยู่เสมอและหลีกเลี่ยงการโยนรหัสใหม่จากด้านบนของหัวของเราโดยประมาท

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

ทำความเข้าใจพื้นฐาน

การทดสอบหน่วยคืออะไร?

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

จะทำการทดสอบหน่วยได้อย่างไรและมีอะไรบ้าง?

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

การทดสอบการรวมคืออะไร?

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

คำแนะนำอย่างละเอียดถี่ถ้วนสำหรับไลบรารี Android ที่ใช้งานน้อยเกินไป

มือถือ

คำแนะนำอย่างละเอียดถี่ถ้วนสำหรับไลบรารี Android ที่ใช้งานน้อยเกินไป
การพัฒนาโครงการ AI - ผู้จัดการโครงการควรเตรียมตัวอย่างไร

การพัฒนาโครงการ AI - ผู้จัดการโครงการควรเตรียมตัวอย่างไร

แนวโน้ม

โพสต์ยอดนิยม
Nvidia Shield - สิ่งที่แตกต่างบนคอนโซลเกม Android
Nvidia Shield - สิ่งที่แตกต่างบนคอนโซลเกม Android
แผ่นโกงการจัดการโครงการ
แผ่นโกงการจัดการโครงการ
เริ่มต้นใช้งาน Microservices: บทช่วยสอน Dropwizard
เริ่มต้นใช้งาน Microservices: บทช่วยสอน Dropwizard
การแยกการเรียกเก็บเงิน: เรื่องของการเพิ่มประสิทธิภาพ API ภายใน GraphQL
การแยกการเรียกเก็บเงิน: เรื่องของการเพิ่มประสิทธิภาพ API ภายใน GraphQL
กรณีศึกษา: การใช้ ApeeScape เพื่อม้วนปลาใหญ่
กรณีศึกษา: การใช้ ApeeScape เพื่อม้วนปลาใหญ่
 
การประมาณต้นทุนซอฟต์แวร์ในการจัดการโครงการแบบ Agile
การประมาณต้นทุนซอฟต์แวร์ในการจัดการโครงการแบบ Agile
แชทล่ม - เมื่อ Chatbot ล้มเหลว
แชทล่ม - เมื่อ Chatbot ล้มเหลว
ที่ปรึกษาการระดมทุนกับนายหน้า - ตัวแทนจำหน่าย
ที่ปรึกษาการระดมทุนกับนายหน้า - ตัวแทนจำหน่าย
ทำให้ Web Front-end เชื่อถือได้ด้วย Elm
ทำให้ Web Front-end เชื่อถือได้ด้วย Elm
คู่มือสำหรับนักลงทุนเกี่ยวกับน้ำมันปาล์ม
คู่มือสำหรับนักลงทุนเกี่ยวกับน้ำมันปาล์ม
โพสต์ยอดนิยม
  • วิธีโดนแฮกหมายเลขบัตรเครดิต
  • ตัวอย่างการออกแบบแอพมือถือที่ไม่ดี
  • กฎเกสตัลต์ของแพรญญานซ์เสนอว่าระบบการมองเห็น
  • เกิดอะไรขึ้นกับบริษัทแบล็คเบอร์รี่
  • แปลงเงินเดือนเป็นอัตราตามสัญญา
  • การโจมตีทางเว็บประเภทใดที่ใช้ฟังก์ชันรับและโพสต์ของรูปแบบ html
หมวดหมู่
  • การจัดการวิศวกรรม
  • Kpi และ Analytics
  • เทคโนโลยี
  • ว่องไว
  • © 2022 | สงวนลิขสิทธิ์

    portaldacalheta.pt