2009-06-01 19 views
13

tôi đã thiết kế một ứng dụng sử dụng các mô hình kho, và sau đó một lớp dịch vụ riêng biệt như thế này:TDD - Bạn muốn kiểm tra Lớp dịch vụ của mình bằng một Kho lưu trữ giả, nhưng làm cách nào?

public class RegistrationService: IRegistrationService 
{ 
    public void Register(User user) 
    { 
     IRepository<User> userRepository = new UserRepository(); 
     // add user, etc 
    } 
} 

Như bạn thấy, tôi đang instantiating kho của tôi bên trong của phương pháp Đăng ký. Bây giờ khi tôi muốn viết một số bài kiểm tra đơn vị, tôi không thể thực sự nhận được nó và thay thế nó bằng một kho lưu trữ giả có thể tôi?

Tôi không muốn thêm kho lưu trữ dưới dạng biến lớp (và đặt thông qua hàm tạo) vì tôi nghĩ điều đó sẽ làm cho mã của tôi "có mùi" (không phải tất cả kho đều cần cho tất cả các phương pháp và tôi không t cần lớp gọi để biết về kho lưu trữ, vv).

Đề xuất?

+1

Bạn đang đi đúng hướng, nhưng chuyển sang một phụ thuộc thông qua hàm tạo không làm cho mã của bạn có mùi. Nó là một hiệu trưởng thiết kế được chấp nhận (Inversion of Control). – ebrown

+0

Đây là một cuộc nói chuyện thực sự tốt về Thử nghiệm Đơn vị với DI: http://www.youtube.com/watch?v=wEhu57pih5w –

Trả lời

16

Bạn cần sử dụng Dependency Injection. UserRepository là một sự phụ thuộc của lớp RegistrationService của bạn. Để làm cho các lớp của bạn có thể kiểm tra được một cách chính xác (tức là cô lập các phụ thuộc của chúng), bạn cần "đảo ngược" những gì kiểm soát việc tạo phụ thuộc của bạn. Hiện tại, bạn có quyền kiểm soát trực tiếp và đang tạo chúng trong nội bộ. Chỉ cần đảo ngược điều khiển đó, và cho phép một cái gì đó bên ngoài (chẳng hạn như một container IoC như Castle Windsor) tiêm chúng:

public class RegistrationService: IRegistrationService 
{ 
    public RegistrationService(IRepository<User> userRepo) 
    { 
     m_userRepo = userRepo; 
    } 

    private IRepository<User> m_userRepo; 

    public void Register(User user) 
    { 
     // add user, etc with m_userRepo 
    } 
} 

Tôi nghĩ rằng tôi quên thêm. Một khi bạn cho phép phụ thuộc của bạn được tiêm, bạn mở ra khả năng sở hữu của chúng là chế nhạo. Vì RegistrationService của bạn bây giờ lấy kho lưu trữ người dùng phụ thuộc của nó làm đầu vào cho hàm tạo của nó, bạn có thể tạo một kho lưu trữ giả và chuyển nó vào, thay vì một kho lưu trữ thực tế.

+0

Tôi đoán đây là cách để đi sau đó ... Tôi không muốn có IRepository của tôi như là một biến lớp, nhưng tôi đoán cho tiêm nó là con đường để đi và sau đó quá tải các nhà xây dựng. Cảm ơn! – Alex

+0

Thay vì tiêm repo chính nó, bạn có thể tiêm một nhà máy, và tạo ra repo theo yêu cầu từ đó. – jrista

0

Bạn có thể sử dụng một thùng chứa IOC và sau đó đăng ký một kho lưu trữ khác.

9

Dependency Injection có thể đến trong rất tiện dụng cho các loại điều:

public class RegistrationService: IRegistrationService 
{ 
    public void Register(User user) 
    { 
     this(user, new UserRepository()); 
    } 

    public void Register(User user, IRepository<User> userRepository) 
    { 
     // add user, etc 
    } 

} 

Bằng cách này bạn sẽ có được khả năng sử dụng UserRepository như mặc định của bạn, và vượt qua trong một tùy chỉnh (Mock hoặc Stub để thử nghiệm) IRepository cho bất cứ điều gì khác bạn cần. Bạn cũng có thể muốn thay đổi phạm vi của phương pháp đăng ký thứ hai trong trường hợp bạn không muốn để lộ nó cho tất cả mọi thứ.

Một lựa chọn tốt hơn nữa là sử dụng một thùng chứa IoC để mua cú pháp của bạn thậm chí còn tách rời hơn ví dụ trên. Bạn có thể nhận được một cái gì đó như thế này:

public class RegistrationService: IRegistrationService 
{ 
    public void Register(User user) 
    { 
     this(user, IoC.Resolve<IRepository<User>>()); 
    } 

    public void Register(User user, IRepository<User> userRepository) 
    { 
     // add user, etc 
    } 

} 

Có rất nhiều container IoC trên mạng, như được liệt kê bởi câu trả lời của người khác, hoặc bạn có thể roll your own.

+0

Điều này có vẻ khá tốt nhưng có nhược điểm là tôi sẽ cần phải tạo phương pháp thứ 2 với tham số userRepository cho mọi phương thức trong RegistrationService ... – Alex

+1

@Alex Đúng vậy. Nếu bạn không muốn làm điều đó, bạn có thể xen kẽ các kho lưu trữ trong constructor của bạn, nhưng bạn đã shying đi từ đó trong câu hỏi của bạn. – Joseph

+0

@Alex Tuy nhiên, cũng lưu ý rằng một trong các phương pháp chỉ là trình bao bọc để xác định hành vi "mặc định" và không có bất kỳ hành vi thực sự nào trong đó. – Joseph

0

Đề xuất của tôi vẫn sẽ di chuyển kho lưu trữ đến tham số lớp và sử dụng vùng chứa IoC để tiêm kho lưu trữ khi cần. Bằng cách này, mã đơn giản và có thể kiểm chứng.

2

Điều bạn nên làm là sử dụng Inversion of Control hoặc Dependency injection.

Lớp học của bạn không được gắn với việc triển khai lớp khác, đặc biệt là trên các lớp như thế này, thay vào đó nó sẽ lấy một giao diện đại diện cho kho lưu trữ của người dùng làm công cụ xây dựng hoặc thuộc tính. Sau đó, khi nó chạy, bạn cung cấp việc triển khai cụ thể và nó sử dụng nó. Bạn có thể xây dựng hàng giả sau đó thực hiện giao diện kho lưu trữ và cung cấp chúng tại thời điểm thử nghiệm.

Có rất nhiều IOC container, Unity, Ninject, Castle Windsor và StructureMap để đặt tên một vài.


Tôi chỉ muốn được rõ ràng, trong các bài kiểm tra đơn vị của bạn, tôi không biết rằng bạn muốn thực sự sử dụng IOC Container. Kiểm tra tích hợp, chắc chắn, nhưng đối với các bài kiểm tra đơn vị, chỉ cần mới lên đối tượng bạn đang thử nghiệm và cung cấp giả của bạn trong quá trình đó. Các thùng chứa IOC, ít nhất là IMO, ở đó để giúp đỡ ở cấp độ ứng dụng hoặc thử nghiệm tích hợp và làm cho cấu hình ở đó dễ dàng hơn.

Bài kiểm tra đơn vị của bạn có lẽ nên ở trạng thái 'thuần' và chỉ hoạt động với các đối tượng bạn đang cố kiểm tra. Ít nhất đó là những gì làm việc cho tôi.

1

Chỉ cần thêm một chút rõ ràng (hoặc có thể không) cho những gì mọi người đã nói, cách IOC hoạt động cơ bản là bạn yêu cầu thay thế X bằng Y. Vì vậy, những gì bạn sẽ làm là chấp nhận IRepository (như những người khác đã nêu) như một tham số và sau đó trong IOC, bạn sẽ yêu cầu nó thay thế IRepository bằng MockedRepository (ví dụ) cho lớp cụ thể đó.

Nếu bạn sử dụng một IoC cho phép khởi tạo web.config, nó làm cho việc chuyển đổi từ dev thành prod rất trơn tru. Bạn chỉ cần sửa đổi các thông số thích hợp trong tệp cấu hình và bạn đang trên đường đi - mà không cần phải chạm vào mã. Sau đó, nếu bạn quyết định bạn muốn chuyển sang nguồn dữ liệu khác - bạn chỉ cần chuyển đổi nó ra và bạn đang trên đường đi.

IoC là một số nội dung thú vị.

Một lưu ý phụ, bạn có thể làm điều đó mà không cần tiêm IOC nếu tất cả những gì bạn muốn làm là Kiểm tra đơn vị. Bạn có thể làm điều này bằng cách nạp chồng quá trình xây dựng để chấp nhận một IRepository và chỉ cần chuyển repo giả của bạn theo cách đó.

+0

Cảm ơn bạn đã cung cấp thêm thông tin về IOC :) – Alex

1

Tôi đang làm việc trên một cái gì đó, tốt, gần như nguyên văn tại thời điểm này. Tôi đang sử dụng MOQ để giả lập một cá thể proxy của IUserDataRepository của tôi.

Từ quan điểm thử nghiệm:

//arrange 
var mockrepository = new Mock<IUserDataRepository>(); 
// InsertUser method takes a MembershipUser and a string Role 
mockrepository.Setup(repos => repos.InsertUser(It.IsAny<MembershipUser>(), It.IsAny<string>())).AtMostOnce(); 

//act 
var service = new UserService(mockrepository.Object); 
var createResult = service.CreateUser(new MembershipUser(), "WebUser"); 

//assert 
Assert.AreEqual(MembershipCreateUserResult.Success, createResult); 
mockrepository.VerifyAll(); 

MOQ cũng có thể được sử dụng để ném ngoại lệ cụ thể cũng vì vậy mà UserService của tôi có thể được kiểm tra dưới những tình huống này.

Sau đó, giống như những người khác đã đề cập đến IoC sẽ xử lý sự thiếu hụt bắt buộc. Trong trường hợp này tạo một kho lưu trữ thực sự cho lớp UserService.

Các vấn đề liên quan