มีค่าจริงในการทดสอบหน่วยควบคุมใน ASP.NET MVC หรือไม่?


33

ฉันหวังว่าคำถามนี้จะให้คำตอบที่น่าสนใจเพราะเป็นคำถามที่ทำให้ฉันอึดใจ

มีค่าจริงในการทดสอบหน่วยควบคุมใน ASP.NET MVC หรือไม่?

สิ่งที่ฉันหมายถึงโดยส่วนใหญ่แล้ว (และฉันไม่มีอัจฉริยะ) วิธีการควบคุมของฉันคือแม้สิ่งที่ซับซ้อนที่สุดของพวกเขาเช่นนี้:

public ActionResult Create(MyModel model)
{
    // start error list
    var errors = new List<string>();

    // check model state based on data annotations
    if(ModelState.IsValid)
    {
        // call a service method
        if(this._myService.CreateNew(model, Request.UserHostAddress, ref errors))
        {
            // all is well, data is saved, 
            // so tell the user they are brilliant
            return View("_Success");
        }
    }

    // add errors to model state
    errors.ForEach(e => ModelState.AddModelError("", e));

    // return view
    return View(model);
}

การยกของหนักส่วนใหญ่ทำได้โดยไปป์ไลน์ MVC หรือไลบรารีบริการของฉัน

ดังนั้นคำถามที่อาจถามคือ:

  • มูลค่าของการทดสอบหน่วยวิธีนี้จะเป็นอย่างไร
  • มันจะไม่ทำลายบนRequest.UserHostAddressและModelStateมี NullReferenceException หรือไม่? ฉันควรลองล้อเลียนสิ่งเหล่านี้หรือไม่
  • ถ้าฉันหักเหวิธีนี้เป็น "ตัวช่วย" ที่สามารถใช้ซ้ำได้ (ซึ่งฉันอาจจะต้องพิจารณากี่ครั้งที่ฉันทำมัน!) จะทำการทดสอบที่คุ้มค่าเมื่อทุกอย่างที่ฉันกำลังทดสอบจริงๆคือ "ขั้นตอน" ซึ่งส่วนใหญ่ สันนิษฐานได้รับการทดสอบเพื่อภายในนิ้วของชีวิตโดย Microsoft?

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

[TestMethod]
public void Test_Home_Index()
{
    var controller = new HomeController();
    var expected = "Index";
    var actual = ((ViewResult)controller.Index()).ViewName;
    Assert.AreEqual(expected, actual);
}

เห็นได้ชัดว่าฉันกำลังป้านกับตัวอย่างที่ไม่มีจุดหมายเกินจริงนี้ แต่ไม่มีใครมีภูมิปัญญาที่จะเพิ่มที่นี่?

หวังว่าจะได้ ... ขอบคุณ


ฉันคิดว่า ROI (ผลตอบแทนจากการลงทุน) ในการทดสอบนั้นไม่คุ้มค่ากับความพยายามเว้นแต่คุณจะมีเวลาและเงินที่ไม่สิ้นสุด ฉันจะเขียนการทดสอบที่เควินชี้ให้เห็นสำหรับการตรวจสอบสิ่งต่าง ๆ ที่มีแนวโน้มที่จะแตกหักหรือจะช่วยคุณในการสร้างสิ่งใหม่ด้วยความมั่นใจหรือมั่นใจว่าการเผยแพร่ข้อผิดพลาดเกิดขึ้นตามที่คาดไว้ การทดสอบท่อถ้าจำเป็นสามารถทำได้ในระดับโลก / โครงสร้างพื้นฐานที่มากขึ้นและในแต่ละระดับวิธีจะมีค่าน้อย ไม่ได้พูดว่าพวกเขาไม่มีค่า แต่ "น้อย" ดังนั้นถ้าเป็นกรณีที่ดีในกรณีของคุณลองไปหาปลาตัวอื่นก่อน!
Mrchief

คำตอบ:


18

แม้จะมีบางอย่างที่เรียบง่ายการทดสอบหน่วยก็ยังสามารถใช้งานได้หลากหลาย

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

สำหรับการกระทำนั้นฉันจะทดสอบสิ่งต่อไปนี้

  1. จะเกิดอะไรขึ้นถ้า _myService เป็นโมฆะ?
  2. จะเกิดอะไรขึ้นถ้า _myService.Create ส่งข้อยกเว้นมันจะโยนสิ่งที่เฉพาะเจาะจงเพื่อจัดการหรือไม่?
  3. _myService.Create ที่ประสบความสำเร็จสร้างคืนมุมมอง _Success หรือไม่
  4. ข้อผิดพลาดแพร่กระจายถึง ModelState หรือไม่

คุณชี้ให้เห็นการตรวจสอบคำขอและรุ่นสำหรับ NullReferenceException และฉันคิดว่า ModelState.IsValid จะดูแลการจัดการ NullReference สำหรับรุ่น

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


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

Spifty ดีใจที่มันช่วยคุณ
Kevin

3

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

  • สร้างแบบจำลองจากสตริงการสืบค้น HTTP, ค่าแบบฟอร์ม, ฯลฯ
  • ดำเนินการตรวจสอบเบื้องต้น
  • โทรเข้าไปในชั้นข้อมูลหรือธุรกิจของฉัน
  • สร้าง ActionResult

การรวมโมเดลส่วนใหญ่ทำได้โดยอัตโนมัติโดย ASP.NET MVC DataAnnotations จัดการกับการตรวจสอบส่วนใหญ่สำหรับฉันด้วย

แม้จะมีน้อยมากที่จะทดสอบฉันยังคงเขียนพวกเขา โดยพื้นฐานแล้วฉันทดสอบว่าที่เก็บของฉันถูกเรียกใช้และActionResultส่งคืนชนิดที่ถูกต้อง ฉันมีวิธีอำนวยความสะดวกเพื่อViewResultให้แน่ใจว่าจะส่งคืนพา ธ การดูที่ถูกต้องและโมเดลการดูเป็นไปตามที่ฉันคาดไว้ ฉันมีอีกสำหรับการตรวจสอบที่ถูกต้องควบคุม / RedirectToActionResultการดำเนินการตั้งค่าสำหรับ ฉันมีการทดสอบJsonResultอื่น ๆ เป็นต้น

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

นี่ต้องใช้ความขยันมาก แต่ฉันคิดว่ามันคุ้มค่า


ขอบคุณที่สละเวลาตอบ แต่มี 2 ประเด็นทันที ประการแรก "วิธีการช่วยเหลือ" ในการทดสอบหน่วยเป็น v.dangerous ประการที่สอง "ทดสอบว่าที่เก็บข้อมูลของฉันถูกเรียกว่า" - คุณหมายถึงการฉีดพึ่งพาหรือไม่
LiverpoolsNumber9

ทำไมวิธีการอำนวยความสะดวกจะเป็นอันตราย? ฉันมีBaseControllerTestsชั้นเรียนที่พวกเขาทั้งหมดอาศัยอยู่ ฉันเยาะเย้ยที่เก็บของฉัน ฉันเสียบมันโดยใช้ Ninject
สวนสาธารณะเทรวิส

จะเกิดอะไรขึ้นถ้าคุณทำผิดพลาดหรือมีข้อสันนิษฐานผิดพลาดในผู้ช่วย / s? ประเด็นอื่น ๆ ของฉันคือการทดสอบการรวมระบบ (เช่นจากต้นทางถึงปลายทาง) สามารถ "ทดสอบ" ว่าที่เก็บข้อมูลของคุณถูกเรียก ในการทดสอบหน่วยคุณจะ "เพิ่มใหม่" หรือเลียนแบบที่เก็บของคุณด้วยตนเองอยู่ดี
LiverpoolsNumber9

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

โอเคพอยุติธรรม - ฉันเข้าใจผิดเล็กน้อยว่าคุณหมายถึงอะไรโดย "ทดสอบว่าที่เก็บของฉันถูกเรียกว่า"
LiverpoolsNumber9

2

เห็นได้ชัดว่าคอนโทรลเลอร์บางตัวนั้นซับซ้อนกว่านั้นมาก แต่ใช้ตัวอย่างของคุณอย่างหมดจด:

จะเกิดอะไรขึ้นถ้า myService โยนข้อยกเว้น?

ในฐานะที่เป็นบันทึกด้านข้าง

นอกจากนี้ฉันยังถามภูมิปัญญาของการส่งรายการโดยอ้างอิง (มันไม่จำเป็นตั้งแต่ c # ส่งต่อโดยการอ้างอิงอยู่ดีแม้ว่ามันจะไม่ใช่) - ผ่านการกระทำ errorAction (Action) ที่บริการสามารถใช้เพื่อปั๊มข้อความแสดงข้อผิดพลาดไปที่ ซึ่งสามารถจัดการได้อย่างไรก็ตามคุณต้องการ (บางทีคุณอาจต้องการเพิ่มลงในรายการหรือคุณต้องการเพิ่มข้อผิดพลาดของแบบจำลองบางทีคุณอาจต้องการบันทึก)

ในตัวอย่างของคุณ:

แทนที่จะเป็นข้อผิดพลาดในการอ้างอิงให้ทำ (string s) => ModelState.AddModelError ("", s) เช่น


ถ้าพูดถึงบริการนี้จะถือว่าบริการของคุณอยู่ในแอปพลิเคชันเดียวกัน
Michael

บริการจะอยู่ใน dll แยกต่างหาก แต่อย่างไรก็ตามคุณอาจถูก "อ้างอิง" ในอีกประเด็นหนึ่งมันไม่สำคัญว่า myService จะส่งข้อยกเว้น ฉันไม่ได้ทดสอบ myService - ฉันจะทดสอบวิธีการต่าง ๆ ในนั้น ฉันกำลังพูดถึงการทดสอบ "หน่วย" ของ ActionResult กับ (อาจ) myService ที่ล้อเลียน
LiverpoolsNumber9

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

ไม่ได้ในตอนท้ายของวันวิธีการบริการจะรับอินพุต (โดยปกติคือโมเดลการดูหรือแม้กระทั่งแค่สตริง / ints) พวกมัน "ทำสิ่งต่าง ๆ " จากนั้นส่งคืนบูล / ข้อผิดพลาดหากเป็นเท็จ ไม่มีลิงค์ "ตรง" ระหว่างคอนโทรลเลอร์และเลเยอร์บริการ จะถูกแยกออกอย่างสมบูรณ์
LiverpoolsNumber9

ใช่ฉันเข้าใจว่าฉันกำลังพยายามทำความเข้าใจกับโมเดลเชิงสัมพันธ์ระหว่างคอนโทรลเลอร์กับเลเยอร์การบริการโดยสมมติว่าคอนโทรลเลอร์แต่ละตัวไม่มีวิธีการบริการที่สอดคล้องกันจากนั้นจะต้องมีเหตุผลที่คอนโทรลเลอร์บางตัวอาจจำเป็นต้องใช้ มากกว่าหนึ่งวิธีการบริการ?
Michael
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.