2012-02-22 12 views
7

thể trùng lặp:
Using IoC for Unit TestingKết hợp Unit Tests (chế giễu) và một khung tiêm dependecy

Tôi nghĩ rằng tôi có một vấn đề hiểu biết về thử nghiệm cách Unit và/hoặc Dependency Injection là đang làm việc. Tôi đang sử dụng NUnit và Rhino Mocks để kiểm tra Đơn vị và Ninject như một Khuôn khổ Khuôn khổ phụ thuộc. Nói chung, tôi mặc dù cả hai sẽ phù hợp với perfeclty - nhưng bằng cách nào đó, nó có vẻ như nó được chỉ phức tạp hơn và khó hiểu hơn.

(Tôi sẽ cố gắng tạo nên một ví dụ tốt, để giữ cho nó sạch sẽ và dễ dàng. Đó là về tôi, đi xe đạp).

1.) Nếu không có DI/Unit Tests:
Mà không biết của DI và đơn vị xét nghiệm, mã của tôi đã có thể nhìn như thế - và tôi muốn được hạnh phúc:

public class Person 
{ 
    public void Travel() 
    { 
     Bike bike = new Bike(); 
     bike.Ride(); 
    } 
} 

public class Bike 
{ 
    public void Ride() 
    { 
     Console.WriteLine("Riding a Bike"); 
    } 
} 

Để đi xe của tôi xe đạp i sẽ chỉ cần: new Person().Travel();

2.) Với DI:
tôi không muốn điều đó khớp nối chặt chẽ, vì vậy tôi cần một giao diện và một NinjectModule! Tôi có một số Overhead, nhưng điều đó sẽ ổn, miễn là mã dễ đọc và dễ hiểu. Tôi sẽ chỉ vượt qua mã cho lớp người biến đổi, lớp Bike là không thay đổi:

public class Person 
{ 
    IKernel kernel = new StandardKernel(new TransportationModule()); 
    public void Travel() 
    { 
     ITransportation transportation = kernel.Get<ITransportation>(); 
     transportation.Ride(); 
    } 
} 

tôi vẫn có thể đi xe đạp của tôi với chỉ: new Person().Travel();

3.) Xét Unit-Testing (không có DI):
Để có thể kiểm tra xem Phương pháp đi xe có được gọi đúng cách hay không, tôi sẽ cần một Mô hình. Theo tôi biết, có hai cách để tiêm một giao diện: Tiêm xây dựngSetter Injection. Tôi chọn Constructor tiêm ví dụ của tôi:

public class Person 
{ 
    ITransportation transportation; 

    public person(ITransportation transportation) 
    { 
     this.transportation = transportation; 
    } 

    public void Travel() 
    { 
     transportation.Ride(); 
    } 
} 

Lần này, tôi sẽ neet để vượt qua xe đạp: new Person(new Bike()).Travel();

4.) Với DI và chuẩn bị cho đơn vị xét nghiệm
Lớp trong 3. Xét đơn vị kiểm tra (không có DI) sẽ thực hiện công việc mà không sửa đổi, nhưng tôi cần phải gọi new Person(kernel.Get<ITransportation>());. Thông qua đó, nó cảm thấy như tôi đang mất lợi ích từ DI - lớp người có thể gọi du lịch mà không cần bất kỳ khớp nối và bất kỳ cần phải biết loại lớp giao thông vận tải là. Ngoài ra, tôi nghĩ rằng biểu mẫu này thiếu nhiều khả năng đọc của ví dụ 2.

Đây có phải là cách thực hiện không? Hoặc là có cách nào khác - thanh lịch hơn để đạt được Dependency Injection và khả năng kiểm tra đơn vị (và giả lập)?

(Nhìn lại, có vẻ như ví dụ này thực sự tồi tệ - mọi người nên biết loại thiết bị giao thông nào anh ta đang cưỡi tại thời điểm này ...)

+0

Tôi nghĩ bạn có thể bị nhầm lẫn về các định nghĩa về Dependency Injection (DI) và Inversion of Control (IoC). Bạn là con số thứ ba * thực hiện * thực hiện DI (bạn đã di chuyển phụ thuộc vào hàm tạo), con số thứ hai của bạn đang sử dụng thùng chứa IoC (Ninject) để giải quyết các phụ thuộc. –

+0

zapthedingbat là đúng. Bạn đang thực hiện DI trong ví dụ thứ ba, nhưng không sử dụng vùng chứa DI, điều này là tốt. DI container là tùy chọn khi thực hiện DI. – Steven

+1

Ví dụ "2." không sử dụng DI, nhưng là "Dịch vụ định vị" ... –

Trả lời

10

Nói chung tôi cố gắng tránh sử dụng một thùng chứa IoC để kiểm tra đơn vị của mình - chỉ cần sử dụng mocks và sơ khai để chuyển vào các phụ thuộc.

Sự cố của bạn bắt đầu trong trường hợp 2: Đây là không phải DI - đây là service locator (anti-)pattern. Đối với phụ thuộc thực sự Tiêm, bạn cần chuyển phụ thuộc của bạn, tốt hơn là thông qua tiêm xây dựng.

Trường hợp 3 có vẻ tốt, đây là DI và nói chung bạn cũng được kích hoạt như thế nào để kiểm tra các lớp học của mình trong cách ly - chuyển các phụ thuộc bạn cần. Tôi hiếm khi thấy cần phải sử dụng một thùng chứa DI đầy đủ để kiểm tra đơn vị vì mỗi lớp được thử nghiệm sẽ chỉ có một vài phụ thuộc, mỗi trong số đó có thể được bố trí hoặc chế nhạo để thực hiện thử nghiệm.

Tôi thậm chí còn cho rằng nếu bạn cần một thùng chứa IoC, các thử nghiệm của bạn có thể không đủ chi tiết hoặc bạn có quá nhiều phụ thuộc. Trong trường hợp sau này, một số phép tái cấu trúc có thể là để tạo thành các lớp tổng hợp từ hai hoặc nhiều phụ thuộc bạn đang sử dụng (chỉ khi có bất kỳ kết nối ngữ nghĩa nào của khóa học). Điều này cuối cùng sẽ giảm số lượng phụ thuộc vào một mức độ mà bạn cảm thấy thoải mái. Con số tối đa đó là khác nhau đối với mỗi người, cá nhân tôi cố gắng có tối đa 4 người, ít nhất tôi có thể đếm chúng bằng một tay và chế nhạo không phải là quá nhiều gánh nặng.

Đối số cuối cùng và quan trọng đối với việc sử dụng vùng chứa IoC trong thử nghiệm đơn vị là kiểm tra hành vi: Làm thế nào bạn có thể chắc chắn lớp đang kiểm tra hoạt động theo cách bạn muốn nếu bạn không kiểm soát hoàn toàn các phụ thuộc của mình?

Có thể cho rằng bạn có thể đạt được điều này bằng cách loại bỏ tất cả các phụ thuộc với các loại đặt cờ cho các hành động nhất định, nhưng đây là một nỗ lực lớn. Nó là nhiều, dễ dàng hơn nhiều để sử dụng một khuôn khổ mocking như RhinoMocks hoặc Moq để xác minh rằng một số phương pháp được gọi với các đối số bạn chỉ định. Để làm được điều đó, bạn cần giả lập các phụ thuộc mà bạn muốn thẩm định các cuộc gọi, một container IoC không thể giúp bạn ở đây.

4

Bạn đang nhận được một số điều bối rối.

Triển khai 3 tốt hơn sau đó là số 2, vì bạn không cần phải thiết lập khung DI trong các bài kiểm tra đơn vị của mình.

Vì vậy, khi kiểm tra số 3 bạn sẽ làm gì:

ITransportation transportationMock = MockRepository.GenerateStricktMock<ITransportation>(); 

// setup exceptations on your mock 

var person = new Person(transportationMock); 

Khung DI là cái gì đó là chỉ cần thiết khi xây dựng cây đối tượng trong mã sản xuất. Trong mã thử nghiệm của bạn, bạn có toàn quyền kiểm soát những gì bạn muốn kiểm tra. Khi đơn vị kiểm tra một lớp, bạn thử tất cả các phụ thuộc.

Nếu bạn cũng muốn thực hiện một số thử nghiệm tích hợp, bạn sẽ vượt qua một chiếc xe đạp thực sự đến lớp học của bạn và kiểm tra nó.

Ý tưởng về các lớp thử nghiệm trong tổng số cách ly là bạn có thể kiểm soát từng đường dẫn mã. Bạn có thể làm cho sự phụ thuộc trả về các giá trị chính xác hoặc không chính xác hoặc thậm chí bạn có thể ném nó ra một ngoại lệ. Nếu mọi thứ đang hoạt động và bạn có một mã bảo hiểm tốt đẹp chỉ từ các bài kiểm tra đơn vị của bạn, bạn sẽ chỉ cần một vài bài kiểm tra lớn hơn để đảm bảo rằng DI của bạn được nối đúng.

Chìa khóa để viết mã có thể kiểm tra là để tách đối tượng tạo ra khỏi logic nghiệp vụ.

3

My 2 cents ...

Mặc dù điểm 2 là một ví dụ về sự phụ thuộc Inversion Nguyên tắc (DIP), nó sử dụng Pattern Dịch vụ Vị trí, chứ không phải là Dependency Injection.

Điểm của bạn 3 Minh họa Dependency Injection, nơi mà thùng chứa IoC sẽ tiêm phụ thuộc (ITransportation) vào hàm khởi tạo trong khi xây dựng Person.

Trong ứng dụng thực tế của bạn VÀ thử nghiệm đơn vị, bạn sẽ muốn sử dụng vùng chứa IoC để xây dựng Person cũng như (tức là không phải người mới trực tiếp). Sử dụng mẫu định vị dịch vụ (kernel.Get<Person>();) hoặc DI (ví dụ: Setter) nếu Khung thử nghiệm đơn vị của bạn hỗ trợ điều này.

Điều này sau đó sẽ Build Up Person VÀ Dependencies của nó (tức là lớp bê tông cấu hình cho ITransportation) và Tiêm đó vào Person cũng (rõ ràng, trong các đơn vị kiểm tra IoC của bạn sẽ được cấu hình cho chế giễu/stubbed ITransportation)

Cuối cùng, đó là các phụ thuộc mà bạn muốn Mock, tức là ITransportation, để bạn có thể kiểm tra phương thức Transport() của Person.

Vì xe đạp không có phụ thuộc, nó có thể được kiểm tra trực tiếp/độc lập (bạn không cần thử mô hình Bike.Ride() trừ khi phụ thuộc được thêm vào xe đạp).