2010-08-21 42 views
24

Chúng tôi đang sử dụng Moq để kiểm tra các lớp dịch vụ của chúng tôi, nhưng bị mắc kẹt về cách kiểm tra các tình huống mà một phương thức dịch vụ gọi một phương thức dịch vụ khác của cùng một lớp. Tôi đã thử thiết lập các phương pháp được gọi là ảo, nhưng vẫn không thể tìm ra những gì để làm sau đó trong Moq. Ví dụ:Sử dụng Moq để ghi đè các phương thức ảo trong cùng một lớp

public class RenewalService : IRenewalService 
{ 
    //we've already tested this 
    public virtual DateTime? GetNextRenewalDate(Guid clientId) 
    { 
     DateTime? nextRenewalDate = null; 
     //...<snip> a ton of already tested stuff... 

     return nextRenewalDate; 
    } 

    //but want to test this without needing to mock all 
    //the methods called in the GetNextRenewalDate method 
    public bool IsLastRenewalOfYear(Renewal renewal) 
    { 
     DateTime? nextRenewalDate = GetNextRenewalDate(renewal.Client.Id); 
     if (nextRenewalDate == null) 
      throw new Exceptions.DataIntegrityException("No scheduled renewal date, cannot determine if last renewal of year"); 
     if (nextRenewalDate.Value.Year != renewal.RenewDate.Year) 
      return true; 
     return false; 
    } 
} 

Trong ví dụ trên, phương pháp GetNextRenewalDate của chúng tôi khá phức tạp và chúng tôi đã kiểm tra đơn vị đó. Tuy nhiên, chúng tôi muốn thử nghiệm IsLastRenewalOfYear đơn giản hơn mà không cần phải giả lập mọi thứ cần thiết cho GetNextRenewalDate. Về cơ bản, chúng tôi chỉ muốn thử GetNextRenewalDate.

Tôi nhận ra rằng tôi có thể tạo một lớp mới ghi đè GetNextRenewalDate và kiểm tra lớp mới, nhưng có cách nào mà tôi có thể tận dụng Moq để làm cho điều này đơn giản hơn không?

Trả lời

41

Bạn có thể sử dụng chế nhạo một phần trong kịch bản này, mặc dù tất cả các phương pháp của bạn sẽ cần phải ảo:

var mock = new Moq.Mock<RenewalService>(); 
    mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null); 
    mock.CallBase = true; 
    var results = mock.Object.IsLastRenewalOfYear(...); 
+0

Điều này dường như hoạt động ... Tôi thậm chí không cần phải làm mọi thứ ảo (tôi chỉ giữ nó giống như trên). – Andrew

+3

@Andrew. Các công trình trên vì 'Mock.CallBase == true' có nghĩa là các lời gọi không khớp với một thiết lập sẽ gọi thực hiện bên dưới. Vì vậy, 'IsLastRenewalOfYear' sẽ gọi triển khai thực hiện, vì nó không phải ảo, nhưng' GetNextRenewalDate' sẽ trả về 'null' vì thiết lập sẽ luôn luôn được khớp. Đoạn mã sau sẽ hoạt động ngay cả khi 'IsLastRenewalOfYear' ** là ** virtual. –

+1

chúng tôi sử dụng điều này ở một vài nơi khác nữa, làm việc như một sự quyến rũ. Cảm ơn! – Andrew

0

Chỉnh sửa: Tôi đồng ý rằng đây không phải là câu trả lời đúng cho trường hợp của Andrew. Tôi muốn để lại câu trả lời ở đây cho chuỗi ý kiến. Xin đừng xuống bỏ phiếu nữa :)

Trước khi chỉnh sửa:

khuôn khổ đối tượng thường giả không được thiết kế để đơn giản hóa các kịch bản lớp duy nhất, chúng được thiết kế để cô lập mã của bạn, do đó bạn có thể kiểm tra một lớp duy nhất.

Nếu bạn cố gắng sử dụng khung đối tượng giả để giải quyết vấn đề này, khung công tác sẽ chỉ tạo một lớp dẫn xuất và quá tải phương thức đó. Điều duy nhất khác nhau là bạn có thể làm điều đó trong khoảng 3 dòng thay vì 5, bởi vì bạn sẽ không phải tạo ra định nghĩa lớp dẫn xuất.

Nếu bạn muốn sử dụng các đối tượng giả để cô lập hành vi này, thì bạn nên chia nhỏ lớp này một chút. Logic GetNextRenewalDate có thể nằm bên ngoài đối tượng RenewalService.

Thực tế là bạn đang gặp sự cố này có thể cho thấy rằng có thiết kế chi tiết đơn giản hoặc tinh vi hơn chưa được khám phá. Tìm một lớp ít bê tông hơn, với tên như "người quản lý" hoặc "dịch vụ" thường là một gợi ý rằng bạn có thể chia nhỏ thiết kế của mình thành các lớp nhỏ hơn và có khả năng sử dụng lại và bảo trì tốt hơn.

+0

Tôi không chắc chắn nếu phá vỡ thiết kế của chúng tôi sẽ làm cho nó dễ bảo trì hơn .. Nếu chúng tôi đã đi từ 5.000 lớp học (khoảng những gì chúng tôi hiện có) đến 10.000 lớp học, sẽ khó có thể dễ dàng điều hướng hơn. – Andrew

+0

phá vỡ thiết kế đơn giản để làm cho nó dễ kiểm tra hơn trong một bộ công cụ cụ thể có vẻ như là một ý tưởng tồi. Đi xuống con đường này, bạn hiếm khi có các phương thức gọi các phương thức công khai khác trong một lớp. Làm thế nào là thiết kế tốt? –

+0

@Andrew, Jess: Tôi không biết ứng dụng của anh ấy hoặc yêu cầu của anh ấy, vì vậy hãy thoải mái thực hiện hoặc để lại lời khuyên của tôi. Có, phá vỡ nó chỉ để có một lớp học phương pháp là ngớ ngẩn, nhưng phần còn lại của lời khuyên của tôi vẫn giữ. Thông thường, khi tôi đã nhìn thấy một lớp "quản lý" hoặc "dịch vụ", các tóm tắt không ở đúng nơi. Nếu một trình tái cấu trúc như vậy được thực hiện, các phương thức từ các lớp khác có thể kết thúc ở đây, hoặc các phương thức này có thể kết thúc trong các lớp khác, làm cho lớp này biến mất hoàn toàn. –

0
var mock = new Moq.Mock<RenewalService> { CallBase = true }; 
mock.Setup(m => m.GetNextRenewalDate(It.IsAny<Guid>())).Returns(null); 
var results = mock.Object.IsLastRenewalOfYear(...); 
Các vấn đề liên quan