2010-04-12 41 views
10

我開始使用AutoFixture http://autofixture.codeplex.com/,因爲我的單元測試臃腫了很多數據設置。我花更多時間來完成數據,而不是編寫單元測試。下面是我最初的單元測試的樣子(從貨物申請樣品從DDD藍皮書所採取的示例)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(); 
} 

下面一個例子就是我試圖用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))); 
} 

我想知道是否有更好的方法來重構它。想要做到這一點更短,更容易。

回答

14

您的初次嘗試看起來不錯,但至少有一些事情可以簡化一下。

首先,你應該能夠減少這樣的:

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

這樣:

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

,因爲你不使用的其他變量。但是,這基本上鎖定了CarrierMovement的任何創建,以使用相同的四個值。儘管每個創建的CarrierMovement都將是一個單獨的實例,但它們都將共享相同的四個值,並且我想知道這是否意味着什麼?

在上述同樣,而非

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

你可以寫

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

,因爲你不使用carrierM變量。由於Func的返回類型,類型推斷會發現你正在註冊一個Schedule。

但是,假設附表構造是這樣的:

public Schedule(IEnumerable<CarrierMovement> carrierMovements) 

你可以代替剛剛註冊的carrierMovements這樣的:

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

這將導致AutoFixture正確地自動解決時間表。這種方法更具可維護性,因爲它允許您在將來添加參數到Schedule構造函數而不會中斷測試(只要AutoFixture可以解析參數類型)。

但是,在這種情況下,我們可以做得比這更好,因爲我們實際上並沒有使用carrierMovements變量來完成註冊以外的任何操作。我們真正需要做的只是告訴AutoFixture如何創建IEnumerable<CarrierMovement>的實例。如果你不關心50號(你應該),我們甚至可以使用方法組的語法是這樣的:

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

通知缺少方法調用括號的:我們要註冊一個FUNC鍵和因爲CreateMany<T>方法返回IEnumerable<T>類型推理負責其餘部分。

但是,這些都是細節。在更高級別上,您可能要考慮根本不註冊CarrierMovement。假設這個構造函數:

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

autofixture應該能夠找出它自己。

它將爲每個departureLocation和arrivalLocation創建一個新的Location實例,但這與您在原始測試中手動執行的操作沒有什麼不同。

說到時間,默認AutoFixture使用DateTime.Now,它至少可以確保到達時間永遠不會在出發時間之前。但是,它們很可能是相同的,但如果這是一個問題,則可以始終註冊一個自動遞增函數。

鑑於這些考慮,這裏是一個另類:

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(); 
} 

解決與IList<CarrierMovement>的問題,您將需要註冊。下面是做這件事:

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

不過,既然你問了,我暗示附表構造是這樣的:

public Schedule(IList<CarrierMovement> carrierMovements) 

,我真的覺得你應該重新考慮改變這種API採取的IEnumerable<Carriemovement>。從API設計角度來看,通過任何成員(包括構造函數)提供集合意味着成員可以修改集合(例如通過調用它的Add,Remove和Clear方法)。這幾乎不是你期望從構造函數中獲得的行爲,所以不要允許它。

在我上面的例子中,AutoFixture會自動爲所有Location對象生成新的值,但由於CPU的速度,後續的DateTime實例可能是相同的。

如果您想增加日期時間,您可以編寫一個小型類,每次調用時都會返回返回的DateTime。我會留給類感興趣的讀者的實現,但你可以註冊它像這樣:

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

假設這個API(注意上面再次方法組語法):

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

感謝您的意見。不過,我有一個AutoFixture拋出的小例外 Ploeh.AutoFixture.ObjectCreationException:AutoFixture無法創建System.Collections.Generic.IList'1 [DDDBookingApplication.Domain.Voyage.CarrierMovement]類型的實例,因爲它沒有公開構造函數。 我認爲我應該告訴如何創建CarrierMovement? – 2010-04-12 14:09:47

+0

我也想爲所有instaces有不同的數據集。你的想法是什麼? – 2010-04-12 14:11:51

+0

感謝您的所有細節。現在測試很短,並通過:) – 2010-04-12 15:11:18