6

Tôi bắt đầu tham gia vào Bài kiểm tra đơn vị, Dependancy Injection và tất cả nhạc jazz đó trong khi xây dựng dự án ASP.NET MVC mới nhất của tôi.Làm thế nào để bạn có thể kiểm tra bộ điều khiển của bạn mà không cần một thùng chứa IoC?

Tôi đến thời điểm hiện tại tôi muốn Unit Test my Controllers và tôi đang gặp khó khăn trong việc tìm ra cách thực hiện một cách phù hợp mà không cần thùng chứa IoC.

Đưa ví dụ như một bộ điều khiển đơn giản:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository = new SqlQuestionsRepository(); 

    // ... Continue with various controller actions 
} 

Lớp này không phải là rất đơn vị kiểm chứng vì instantiation trực tiếp của SqlQuestionsRepository. Vì vậy, hãy đi xuống tuyến đường Dependancy Injection và thực hiện:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository; 

    public QuestionsController(IQuestionsRepository repository) 
    { 
     _repository = repository; 
    } 
} 

Điều này có vẻ tốt hơn. Bây giờ tôi có thể dễ dàng viết các bài kiểm tra đơn vị với một IQuestionsRepository giả lập. Tuy nhiên, điều gì sẽ được thực hiện ngay bây giờ? Một nơi nào đó tiếp tục chuỗi cuộc gọi SqlQuestionRepository sẽ phải được khởi tạo. Có vẻ như thông qua tôi chỉ đơn giản là chuyển vấn đề ở nơi khác, không được loại bỏ nó.

Bây giờ, tôi biết đây là một ví dụ điển hình về nơi chứa IoC có thể giúp bạn bằng cách kết nối các phụ thuộc Bộ điều khiển cho tôi trong khi đồng thời giữ bộ điều khiển dễ dàng kiểm tra được.

Câu hỏi của tôi là, làm cách nào để thử nghiệm đơn vị về những thứ có tính chất này mà không cần vùng chứa IoC?

Lưu ý: Tôi không phản đối các thùng chứa IoC, và tôi có thể sẽ sớm đi xuống con đường đó. Tuy nhiên, tôi tò mò về cách thay thế cho những người không sử dụng chúng.

Trả lời

2

Không phải là có thể giữ sự khởi tạo trực tiếp của trường và cũng cung cấp trình thiết lập? Trong trường hợp này, bạn chỉ muốn gọi setter trong khi thử nghiệm đơn vị. Một cái gì đó như thế này:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository _repository = new SqlQuestionsRepository(); 

    // Really only called during unit testing... 
    public QuestionsController(IQuestionsRepository repository) 
    { 
     _repository = repository; 
    } 
} 

Tôi không quá quen thuộc với .NET nhưng đây là một cách phổ biến để cấu trúc lại mã hiện có để cải thiện khả năng kiểm tra. IE., nếu bạn có các lớp đã được sử dụng và cần phải sửa đổi chúng để cải thiện mức độ bao phủ mã mà không vi phạm các chức năng hiện có.

Nhóm của chúng tôi đã thực hiện việc này trước đây và thường chúng tôi đặt khả năng hiển thị của người đặt thành gói riêng và giữ gói của lớp thử nghiệm giống nhau để có thể gọi cho người đặt.

+0

@Peter, đề xuất rất tốt. Bạn đã có bất kỳ kinh nghiệm với việc sử dụng container IoC trên các dự án của bạn hoặc có bạn không tìm thấy một nhu cầu chưa? – mmcdole

+0

Tôi không có nhiều kinh nghiệm .NET, hầu hết công việc của tôi là với Java sử dụng Spring cho IoC. Nó đã rất hữu ích cho việc cải thiện mô đun. – Peter

+0

@Peter: Đây là mẫu khá nhiều mà tôi sử dụng. Tôi đã đăng một câu trả lời mà làm điều tương tự, nhưng sử dụng một số tính năng ngôn ngữ C# mà bạn, là một anh chàng Java, có thể không được nhận thức. –

2

Bạn có thể có một hàm tạo mặc định với bộ điều khiển sẽ có một số loại hành vi mặc định.

Cái gì đó như ...

public QuestionsController() 
    : this(new QuestionsRepository()) 
{ 
} 

Bằng cách đó theo mặc định khi các nhà máy điều khiển được tạo ra một thể hiện mới của bộ điều khiển nó sẽ sử dụng hành vi constructor mặc định của. Sau đó, trong bài kiểm tra đơn vị của bạn, bạn có thể sử dụng một khuôn khổ mocking để vượt qua trong một giả vào constructor khác.

1

Một tùy chọn là sử dụng hàng giả.

public class FakeQuestionsRepository : IQuestionsRepository { 
    public FakeQuestionsRepository() { } //simple constructor 
    //implement the interface, without going to the database 
} 

[TestFixture] public class QuestionsControllerTest { 
    [Test] public void should_be_able_to_instantiate_the_controller() { 
     //setup the scenario 
     var repository = new FakeQuestionsRepository(); 
     var controller = new QuestionsController(repository); 
     //assert some things on the controller 
    } 
} 

Một tùy chọn khác là sử dụng mocks và khung mocking, có thể tự động tạo các mocks này khi đang di chuyển.

[TestFixture] public class QuestionsControllerTest { 
    [Test] public void should_be_able_to_instantiate_the_controller() { 
     //setup the scenario 
     var repositoryMock = new Moq.Mock<IQuestionsRepository>(); 
     repositoryMock 
      .SetupGet(o => o.FirstQuestion) 
      .Returns(new Question { X = 10 }); 
     //repositoryMock.Object is of type IQuestionsRepository: 
     var controller = new QuestionsController(repositoryMock.Object); 
     //assert some things on the controller 
    } 
} 

Về nơi tất cả các đối tượng được xây dựng. Trong một thử nghiệm đơn vị, bạn chỉ thiết lập một tập hợp các đối tượng tối thiểu: một đối tượng thực đang được thử nghiệm và một số phụ thuộc giả hoặc giả lập mà đối tượng thực được kiểm tra yêu cầu. Ví dụ, đối tượng thực dưới thử nghiệm là một thể hiện của QuestionsController - nó có một sự phụ thuộc vào IQuestionsRepository, vì vậy chúng tôi cung cấp cho nó hoặc giả IQuestionsRepository như trong ví dụ đầu tiên hoặc một mô hình IQuestionsRepository như trong ví dụ thứ hai.

Trong hệ thống thực, tuy nhiên, bạn thiết lập toàn bộ vùng chứa ở cấp cao nhất của phần mềm. Trong một ứng dụng Web, ví dụ, bạn thiết lập vùng chứa, nối tất cả các giao diện và các lớp thực hiện, trong GlobalApplication.Application_Start.

0

Tôi đang mở rộng câu trả lời của Peter một chút.

Trong các ứng dụng có nhiều loại thực thể, bộ điều khiển yêu cầu tham chiếu đến nhiều kho lưu trữ, dịch vụ, không phải là điều bất thường. Tôi thấy nó tẻ nhạt để tự vượt qua tất cả những phụ thuộc trong mã thử nghiệm của tôi (đặc biệt là kể từ khi một thử nghiệm nhất định có thể chỉ liên quan đến một hoặc hai trong số họ). Trong những tình huống đó, tôi thích IOC kiểu tiêm-ép-ép hơn so với việc xây dựng. Mẫu tôi sử dụng mẫu này:

public class QuestionsController : ControllerBase 
{ 
    private IQuestionsRepository Repository 
    { 
     get { return _repo ?? (_repo = IoC.GetInstance<IQuestionsRepository>()); } 
     set { _repo = value; } 
    } 
    private IQuestionsRepository _repo; 

    // Don't need anything fancy in the ctor 
    public QuestionsController() 
    { 
    } 
} 

Thay thế IoC.GetInstance<> với bất kỳ cú pháp nào mà khuôn khổ IOC cụ thể của bạn sử dụng.

Trong quá trình sử dụng sản phẩm, không có gì sẽ gọi ra trình thiết lập thuộc tính, vì vậy lần đầu tiên bộ xử lý được gọi là bộ điều khiển sẽ gọi ra khung IOC của bạn, lấy một cá thể và lưu nó.

Trong thử nghiệm, bạn chỉ cần gọi setter trước khi gọi bất kỳ phương pháp điều khiển:

var controller = new QuestionsController { 
    Repository = MakeANewMockHoweverYouNormallyDo(...); 
} 

Những lợi ích của phương pháp này, IMHO:

  1. Tuy nhiên lợi dụng IOC trong sản xuất.
  2. Dễ dàng xây dựng bộ điều khiển theo cách thủ công hơn trong khi thử nghiệm. Bạn chỉ cần khởi tạo các phụ thuộc mà thử nghiệm của bạn sẽ thực sự sử dụng.
  3. Có thể tạo cấu hình IOC thử nghiệm cụ thể, nếu bạn không muốn định cấu hình phụ thuộc thông thường theo cách thủ công.
Các vấn đề liên quan