Có giá trị thực nào trong đơn vị kiểm tra bộ điều khiển trong ASP.NET MVC không?


33

Tôi hy vọng câu hỏi này đưa ra một số câu trả lời thú vị bởi vì nó đã khiến tôi chán nản trong một thời gian.

Có giá trị thực nào trong đơn vị kiểm tra bộ điều khiển trong ASP.NET MVC không?

Điều tôi muốn nói là, hầu hết thời gian, (và tôi không phải là thiên tài), các phương thức điều khiển của tôi, ngay cả ở những thứ phức tạp nhất của chúng như thế này:

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);
}

Hầu hết các công việc nặng được thực hiện bởi đường ống dẫn MVC hoặc thư viện dịch vụ của tôi.

Vì vậy, có lẽ câu hỏi để hỏi có thể là:

  • giá trị của đơn vị thử nghiệm phương pháp này là gì?
  • nó sẽ không phá vỡ Request.UserHostAddressModelStatevới một NullReferenceException? Tôi có nên cố gắng để chế nhạo những?
  • nếu tôi khúc xạ phương pháp này thành một "người trợ giúp" có thể sử dụng lại (mà tôi có lẽ nên làm, xem xét tôi đã làm bao nhiêu lần!), sẽ kiểm tra xem có đáng không khi tất cả những gì tôi thực sự kiểm tra chủ yếu là "đường ống", Có lẽ, đã được Microsoft thử nghiệm trong vòng một inch của cuộc sống?

Tôi nghĩ rằng quan điểm của tôi thực sự là, làm những điều sau đây dường như hoàn toàn vô nghĩa và sai lầm

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

Rõ ràng là tôi đang trở nên mù quáng với ví dụ vô nghĩa quá mức này, nhưng có ai có bất kỳ sự khôn ngoan nào để thêm vào đây không?

Mong chờ nó ... Cảm ơn.


Tôi nghĩ rằng RoI (Lợi tức đầu tư) trong bài kiểm tra cụ thể đó không đáng để bỏ công sức, trừ khi bạn có thời gian và tiền bạc vô hạn. Tôi sẽ viết các bài kiểm tra mà Kevin chỉ ra để kiểm tra những thứ có khả năng bị hỏng hoặc sẽ giúp bạn tái cấu trúc một cái gì đó với sự tự tin hoặc đảm bảo việc truyền lỗi xảy ra như mong đợi. Các thử nghiệm đường ống nếu cần có thể được thực hiện ở cấp độ toàn cầu / cơ sở hạ tầng hơn và ở cấp phương pháp riêng lẻ sẽ ít có giá trị. Không nói rằng chúng không có giá trị, nhưng "ít". Vì vậy, nếu nó cung cấp một RoI tốt trong trường hợp của bạn, hãy tìm nó, nếu không, hãy bắt những con cá lớn hơn trước!
Mrchief 5/2/2016

Câu trả lời:


18

Ngay cả đối với một cái gì đó đơn giản, một bài kiểm tra đơn vị sẽ phục vụ nhiều mục đích

  1. Tự tin, những gì đã được viết phù hợp với đầu ra dự kiến. Có vẻ như tầm thường để xác minh rằng nó trả về quan điểm chính xác, nhưng kết quả là bằng chứng khách quan cho thấy yêu cầu đã được đáp ứng
  2. Kiểm tra hồi quy. Nếu phương thức Tạo cần thay đổi, bạn vẫn có một bài kiểm tra đơn vị cho đầu ra dự kiến. Có, đầu ra có thể thay đổi cùng và điều đó dẫn đến một thử nghiệm dễ vỡ nhưng nó vẫn là một kiểm tra đối với kiểm soát thay đổi không được quản lý

Đối với hành động cụ thể đó, tôi sẽ kiểm tra các mục sau

  1. Điều gì xảy ra nếu _myService là null?
  2. Điều gì xảy ra nếu _myService.Create ném Ngoại lệ, nó có ném những cái cụ thể để xử lý không?
  3. _MyService thành công. Tạo ra có trả về chế độ xem _Success không?
  4. Là lỗi lan truyền lên ModelState?

Bạn đã chỉ ra việc kiểm tra Yêu cầu và Mô hình cho NullReferenceException và tôi nghĩ ModelState.IsValid sẽ đảm nhiệm việc xử lý NullReference cho Model.

Việc loại bỏ Yêu cầu cho phép bạn bảo vệ chống lại Yêu cầu Null thường không thể thực hiện được trong sản xuất, nhưng có thể xảy ra trong Bài kiểm tra đơn vị. Trong Kiểm tra tích hợp, nó sẽ cho phép bạn cung cấp các giá trị UserhostAddress khác nhau (Một yêu cầu vẫn là đầu vào của người dùng khi có liên quan đến kiểm soát và cần được kiểm tra phù hợp)


Xin chào Kevin, cảm ơn vì đã dành thời gian trả lời. Tôi sẽ để nó một lúc để xem có ai khác đến với bất cứ điều gì không nhưng cho đến nay của bạn là hợp lý / rõ ràng nhất.
LiverpoolsNumber9

Spifty. Vui mừng nó đã giúp bạn.
Kevin

3

Bộ điều khiển của tôi là rất nhỏ là tốt. Hầu hết "logic" trong bộ điều khiển được xử lý bằng các thuộc tính bộ lọc (tích hợp và viết tay). Vì vậy, bộ điều khiển của tôi thường chỉ có một số công việc:

  • Tạo mô hình từ chuỗi truy vấn HTTP, giá trị biểu mẫu, v.v.
  • Thực hiện một số xác nhận cơ bản
  • Gọi vào dữ liệu hoặc lớp doanh nghiệp của tôi
  • Tạo một ActionResult

Hầu hết các ràng buộc mô hình được thực hiện tự động bởi ASP.NET MVC. DataAnnotations cũng xử lý hầu hết các xác nhận cho tôi.

Ngay cả với rất ít để kiểm tra, tôi vẫn thường viết chúng. Về cơ bản, tôi kiểm tra rằng các kho lưu trữ của tôi được gọi và ActionResultloại chính xác được trả về. Tôi có một phương pháp thuận tiện để ViewResultđảm bảo đường dẫn chế độ xem đúng được trả về và mô hình khung nhìn trông giống như tôi mong đợi. Tôi có một cái khác để kiểm tra bộ điều khiển / hành động đúng được đặt cho RedirectToActionResult. Tôi có các bài kiểm tra khác JsonResult, v.v.

Một kết quả không may của việc phân Controllerlớp phụ là nó cung cấp rất nhiều phương thức tiện lợi sử dụng HttpContextnội bộ. Điều này làm cho nó khó để kiểm tra đơn vị điều khiển. Vì lý do này, tôi thường đặt HttpContextcác cuộc gọi phụ thuộc phía sau một giao diện và chuyển giao diện đó cho hàm tạo của bộ điều khiển (Tôi sử dụng tiện ích mở rộng web Ninject để tạo bộ điều khiển cho tôi). Giao diện này thường là nơi tôi gắn các thuộc tính của trình trợ giúp để truy cập phiên, cài đặt cấu hình, IPrincipl và trình trợ giúp URL.

Điều này cần rất nhiều sự siêng năng, nhưng tôi nghĩ nó đáng giá.


Cảm ơn đã dành thời gian để trả lời nhưng 2 vấn đề ngay lập tức. Thứ nhất, "phương thức của người trợ giúp" trong các bài kiểm tra đơn vị là v.dangerous. Thứ hai, "kiểm tra rằng kho lưu trữ của tôi được gọi là" - bạn có nghĩa là thông qua tiêm phụ thuộc?
LiverpoolsNumber9

Tại sao các phương pháp tiện lợi sẽ nguy hiểm? Tôi có một BaseControllerTestslớp học nơi tất cả họ sống. Tôi chế nhạo kho của tôi. Tôi cắm chúng vào bằng cách sử dụng Ninject.
Công viên Travis

Điều gì xảy ra nếu bạn mắc lỗi hoặc giả định không chính xác trong người trợ giúp của bạn? Điểm khác của tôi là, chỉ có một bài kiểm tra tích hợp (tức là từ đầu đến cuối) mới có thể "kiểm tra" xem kho của bạn có được gọi hay không. Trong một bài kiểm tra đơn vị, bạn sẽ "mới" hoặc chế nhạo kho lưu trữ của mình bằng tay.
LiverpoolsNumber9

Bạn chuyển kho lưu trữ đến hàm tạo. Bạn chế nhạo nó trong quá trình thử nghiệm. Bạn chắc chắn rằng giả được hành động như mong đợi. Người trợ giúp chỉ cần giải mã cấu ActionResulthình để kiểm tra các URL, mô hình đã qua, v.v.
Công viên Travis

Ok đủ công bằng - Tôi hơi hiểu nhầm ý của bạn là "kiểm tra rằng kho của tôi được gọi là".
LiverpoolsNumber9

2

Rõ ràng một số bộ điều khiển phức tạp hơn thế nhiều nhưng hoàn toàn dựa trên ví dụ của bạn:

Điều gì xảy ra nếu myService ném ngoại lệ?

Như một lưu ý phụ.

Ngoài ra, tôi đặt câu hỏi về sự khôn ngoan của việc chuyển danh sách theo tham chiếu (dù sao cũng không cần thiết vì c # chuyển qua tham chiếu nhưng ngay cả khi không) - chuyển một hành động errorAction (Hành động) mà dịch vụ sau đó có thể sử dụng để bơm thông báo lỗi đến mà sau đó có thể được xử lý theo cách bạn muốn (có thể bạn muốn thêm nó vào danh sách, có thể bạn muốn thêm một lỗi mô hình, có thể bạn muốn đăng nhập nó).

Trong ví dụ của bạn:

thay vì lỗi ref, hãy làm (chuỗi s) => ModelState.AddModelError ("", s) chẳng hạn.


Đáng nói hơn, điều này không cho rằng dịch vụ của bạn đang nằm trong cùng một ứng dụng nếu không các vấn đề nối tiếp sẽ xảy ra.
Michael

Các dịch vụ sẽ được trong một dll riêng biệt. Nhưng dù sao, bạn có thể đúng là "ref". Về mặt khác, không có vấn đề gì nếu myService ném ngoại lệ. Tôi không kiểm tra myService - Tôi sẽ kiểm tra các phương thức trong đó một cách riêng biệt. Tôi đang nói về việc hoàn toàn thử nghiệm "đơn vị" ActionResult với (có thể) một dịch vụ myService bị chế giễu.
LiverpoolsNumber9

Bạn có ánh xạ 1: 1 giữa dịch vụ và bộ điều khiển của bạn không? Nếu không, một số bộ điều khiển sử dụng nhiều cuộc gọi dịch vụ? Nếu vậy, bạn có thể kiểm tra những tương tác?
Michael

Không. Vào cuối ngày, các phương thức dịch vụ nhận đầu vào (thường là mô hình khung nhìn hoặc thậm chí chỉ là chuỗi / ints), chúng "làm công cụ", sau đó trả về bool / lỗi nếu sai. Không có liên kết "trực tiếp" giữa các bộ điều khiển và lớp dịch vụ. Các hoàn toàn tách biệt.
LiverpoolsNumber9

Vâng, tôi hiểu rằng, tôi đang cố gắng hiểu mô hình quan hệ giữa các bộ điều khiển và lớp dịch vụ - giả sử rằng mỗi bộ điều khiển không có một phương thức dịch vụ tương ứng thì sẽ có lý do rằng một số bộ điều khiển có thể cần sử dụng nhiều hơn một phương thức dịch vụ?
Michael
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.