2010-04-12 5 views
10

Tôi bắt đầu sử dụng AutoFixture http://autofixture.codeplex.com/ vì các thử nghiệm đơn vị của tôi bị cồng kềnh với rất nhiều thiết lập dữ liệu. Tôi đã dành nhiều thời gian hơn để thiết lập dữ liệu hơn là viết bài kiểm tra đơn vị của mình. Dưới đây là một ví dụ về cách kiểm tra đơn vị ban đầu của tôi trông như thế (ví dụ lấy từ mẫu ứng dụng hàng hóa từ DDD cuốn sách màu xanh)Tái cấu trúc AutoFixture

[Test] 
public void should_create_instance_with_correct_ctor_parameters() 
{ 
    var carrierMovements = new List<CarrierMovement>(); 

    var deparureUnLocode1 = new UnLocode("AB44D"); 
    var departureLocation1 = new Location(deparureUnLocode1, "HAMBOURG"); 
    var arrivalUnLocode1 = new UnLocode("XX44D"); 
    var arrivalLocation1 = new Location(arrivalUnLocode1, "TUNIS"); 
    var departureDate1 = new DateTime(2010, 3, 15); 
    var arrivalDate1 = new DateTime(2010, 5, 12); 

    var carrierMovement1 = new CarrierMovement(departureLocation1, arrivalLocation1, departureDate1, arrivalDate1); 

    var deparureUnLocode2 = new UnLocode("CXRET"); 
    var departureLocation2 = new Location(deparureUnLocode2, "GDANSK"); 
    var arrivalUnLocode2 = new UnLocode("ZEZD4"); 
    var arrivalLocation2 = new Location(arrivalUnLocode2, "LE HAVRE"); 
    var departureDate2 = new DateTime(2010, 3, 18); 
    var arrivalDate2 = new DateTime(2010, 3, 31); 

    var carrierMovement2 = new CarrierMovement(departureLocation2, arrivalLocation2, departureDate2, arrivalDate2); 

    carrierMovements.Add(carrierMovement1); 
    carrierMovements.Add(carrierMovement2); 

    new Schedule(carrierMovements).ShouldNotBeNull(); 
} 

Dưới đây là làm thế nào tôi đã cố gắng để cấu trúc lại nó với AutoFixture

[Test] 
public void should_create_instance_with_correct_ctor_parameters_AutoFixture() 
{ 
    var fixture = new Fixture(); 

    fixture.Register(() => new UnLocode(UnLocodeString())); 

    var departureLoc = fixture.CreateAnonymous<Location>(); 
    var arrivalLoc = fixture.CreateAnonymous<Location>(); 
    var departureDateTime = fixture.CreateAnonymous<DateTime>(); 
    var arrivalDateTime = fixture.CreateAnonymous<DateTime>(); 

    fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
     (departure, arrival, departureTime, arrivalTime) => new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime)); 

    var carrierMovements = fixture.CreateMany<CarrierMovement>(50).ToList(); 

    fixture.Register<List<CarrierMovement>, Schedule>((carrierM) => new Schedule(carrierMovements)); 

    var schedule = fixture.CreateAnonymous<Schedule>(); 

    schedule.ShouldNotBeNull(); 
} 

private static string UnLocodeString() 
{ 
    var stringBuilder = new StringBuilder(); 

    for (int i = 0; i < 5; i++) 
     stringBuilder.Append(GetRandomUpperCaseCharacter(i)); 

    return stringBuilder.ToString(); 
} 

private static char GetRandomUpperCaseCharacter(int seed) 
{ 
    return ((char)((short)'A' + new Random(seed).Next(26))); 
} 

Tôi muốn biết nếu có cách tốt hơn để cấu trúc lại nó. Muốn làm điều đó ngắn hơn và dễ hơn thế.

Trả lời

14

Nỗ lực ban đầu của bạn có vẻ tốt, nhưng có ít nhất một vài điều bạn có thể đơn giản hóa một chút.

Trước hết, bạn sẽ có thể để giảm này:

fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
    (departure, arrival, departureTime, arrivalTime) => 
     new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime)); 

này:

fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
    () => new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime)); 

kể từ khi bạn không sử dụng các biến khác. Tuy nhiên, điều này về cơ bản khóa bất kỳ tạo ra CarrierMovement để sử dụng cùng bốn giá trị. Mặc dù mỗi CarrierMovement được tạo ra sẽ là một cá thể riêng biệt, tất cả chúng sẽ chia sẻ cùng bốn giá trị, và tôi tự hỏi nếu đó là ý của bạn?

Trong bối cảnh tương tự như trên, thay vì

fixture.Register<List<CarrierMovement>, Schedule>((carrierM) => 
    new Schedule(carrierMovements)); 

bạn có thể viết

fixture.Register(() => new Schedule(carrierMovements)); 

kể từ khi bạn không sử dụng các biến carrierM. Loại hội thảo sẽ tìm ra rằng bạn đang đăng ký một lịch trình vì loại trả về của Func.

Tuy nhiên, giả định rằng các nhà xây dựng Biểu trông như thế này:

public Schedule(IEnumerable<CarrierMovement> carrierMovements) 

bạn có thể thay vì vừa đăng ký carrierMovements như thế này:

fixture.Register<IEnumerable<CarrierMovement>>(carrierMovements); 

đó sẽ gây ra AutoFixture để tự động giải quyết Schedule một cách chính xác. Cách tiếp cận này dễ bảo trì hơn vì nó cho phép bạn thêm một tham số vào hàm tạo lịch biểu trong tương lai mà không phá vỡ phép thử (miễn là AutoFixture có thể giải quyết kiểu tham số).

Tuy nhiên, chúng tôi có thể làm tốt hơn trong trường hợp này vì chúng tôi không thực sự sử dụng biến số carrierMovements cho bất kỳ điều gì khác ngoài đăng ký. Những gì chúng tôi thực sự cần làm là chỉ để nói AutoFixture làm thế nào để tạo ra các trường hợp của IEnumerable<CarrierMovement>. Nếu bạn không quan tâm đến số 50 (bạn không nên), chúng tôi thậm chí có thể sử dụng phương pháp Nhóm cú pháp như thế này:

fixture.Register(fixture.CreateMany<CarrierMovement>); 

Thông báo việc thiếu parantheses gọi phương pháp: chúng tôi đang đăng ký một Func, và vì phương thức CreateMany<T> trả về IEnumerable<T> loại hội thảo sẽ đảm nhiệm phần còn lại.

Tuy nhiên, đó là tất cả chi tiết.Ở cấp độ cao hơn, bạn có thể cân nhắc không đăng ký CarrierMovement. Giả sử hàm tạo này:

public CarrierMovement(Location departureLocation, 
    Location arrivalLocation, 
    DateTime departureTime, 
    DateTime arrivalTime) 

Tự động trộn có thể tự tìm ra.

Nó sẽ tạo một trường hợp Vị trí mới cho mỗi lần khởi hànhVị trí và đếnĐịa điểm, nhưng điều đó không khác với những gì bạn đã thực hiện thủ công trong thử nghiệm ban đầu.

Khi nói đến thời gian, theo mặc định, AutoFixture sử dụng DateTime.Now, ít nhất đảm bảo rằng thời gian đến sẽ không bao giờ trước thời gian khởi hành. Tuy nhiên, chúng rất có thể giống nhau, nhưng bạn luôn có thể đăng ký chức năng tăng tự động nếu đó là vấn đề.

Với những cân nhắc, đây là một sự thay thế:

public void should_create_instance_with_correct_ctor_parameters_AutoFixture() 
{ 
    var fixture = new Fixture(); 

    fixture.Register(() => new UnLocode(UnLocodeString())); 

    fixture.Register(fixture.CreateMany<CarrierMovement>); 

    var schedule = fixture.CreateAnonymous<Schedule>(); 

    schedule.ShouldNotBeNull(); 
} 

Để giải quyết vấn đề với IList<CarrierMovement> bạn sẽ cần phải đăng ký nó. Dưới đây là một cách để làm điều đó:

fixture.Register<IList<CarrierMovement>>(() => 
    fixture.CreateMany<CarrierMovement>().ToList()); 

Tuy nhiên, kể từ khi bạn hỏi, tôi ngụ ý rằng các nhà xây dựng Biểu trông như thế này:

public Schedule(IList<CarrierMovement> carrierMovements) 

và tôi thực sự nghĩ rằng bạn nên xem xét lại việc thay đổi API mà để có một IEnumerable<Carriemovement>. Từ quan điểm thiết kế API, việc cung cấp bộ sưu tập thông qua bất kỳ thành viên nào (bao gồm cả hàm tạo) ngụ ý rằng thành viên được phép sửa đổi bộ sưu tập (ví dụ: bằng cách gọi phương thức Add, Remove và Clear). Đó là hành vi khó mà bạn mong đợi từ một nhà xây dựng, vì vậy đừng cho phép nó.

AutoFixture sẽ tự động tạo các giá trị mới cho tất cả các đối tượng Location trong ví dụ trên, nhưng do tốc độ của CPU, các phiên bản sau của DateTime có thể giống nhau.

Nếu bạn muốn tăng DateTimes, bạn có thể viết một lớp nhỏ làm tăng DateTime trả về mỗi khi nó được gọi. Tôi sẽ để lại việc thực hiện các lớp đó để người đọc quan tâm, nhưng sau đó bạn có thể đăng ký nó như vậy:

var dtg = new DateTimeGenerator(); 
fixture.Register(dtg.Next); 

giả API này (thông báo một lần nữa cú pháp Phương pháp Nhóm trên):

public class DateTimeGenerator 
{ 
    public DateTime Next(); 
} 
+0

Cảm ơn ý kiến ​​của bạn. Tuy nhiên tôi có một ngoại lệ nhỏ được ném bởi AutoFixture Ploeh.AutoFixture.ObjectCreationException: AutoFixture không thể tạo một thể hiện kiểu System.Collections.Generic.IList'1 [DDDBookingApplication.Domain.Voyage.CarrierMovement], vì nó không có công khai constructor. Tôi cho rằng tôi nên nói cách tạo CarrierMovement? –

+0

Tôi cũng muốn có bộ dữ liệu khác nhau cho tất cả các trường hợp. Suy nghĩ của bạn là gì –

+0

Cảm ơn tất cả các chi tiết. Bài kiểm tra ngắn và trôi qua :) –