题
我开始使用AutoFixture http://autofixture.codeplex.com/ 作为我的单元测试是臃肿大量的数据设置。我花更多的时间在塞汀的数据比我单元的测试。这是一个如何我的初始单元的测试,看起来像(例如取货物的样本应用程序从DDD蓝皮书)
[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)));
}
我想知道,如果有更好的方式来重构。想做到这一更短和更容易。
解决方案
您最初的尝试,看起来很好,但至少有几件事情你可以简化一点。
首先,你应该能够减少这样的:
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。
然而,假定的计划的构造是这样的:
public Schedule(IEnumerable<CarrierMovement> carrierMovements)
你可以而不是只有注册的 carrierMovements
是这样的:
fixture.Register<IEnumerable<CarrierMovement>>(carrierMovements);
这将导致AutoFixture自动解决时间表是否正确。这种做法更易于维护的,因为它允许添加一个参数的计划的构造未来,而不破坏试验(如AutoFixture可以解决的参数类型)。
然而,我们可以做得更好,在这种情况下,因为我们真的不用 carrierMovements
变为别的什么比注册。我们真正需要做的只是告诉AutoFixture如何创建的实例 IEnumerable<CarrierMovement>
.如果你不关心的数目50(你不应该),我们甚至可以使用的方法小组的语法是这样的:
fixture.Register(fixture.CreateMany<CarrierMovement>);
注意到缺乏方法调用parantheses:我们注册的一个函数,并且由于 CreateMany<T>
方法返回 IEnumerable<T>
类型推理需要照顾的其余部分。
然而,所有这些都是细节。在一个更高的水平,你可能想要考虑未注册CarrierMovement。假设这个构造:
public CarrierMovement(Location departureLocation,
Location arrivalLocation,
DateTime departureTime,
DateTime arrivalTime)
autofixture应该能够找出本身。
这将创建一个新的位置实例为每departureLocation和arrivalLocation,但是这没有什么不同于什么你的手没有在原始测试。
当涉及到的时间,可通过默认使用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设计的角度来看,提供一个收集过任何成员(包括一个构造)意味着该成员是否允许修改的收集(例如通过援引这是添加、删除和清楚的方法)。这几乎是不行为,你会期望从一个构造的,因此不允许的。
AutoFixture会自动生成新的价值观,为所有 Location
象在我上面的例子,但由于CPU的速度,随后的实例的日期时间很可能相同。
如果你想要增加DateTimes,你可以写一个小类,增加返回日期的每一时间的调用。我会离开实施的这类感兴趣的读者,但是然后您可以登记,像这样:
var dtg = new DateTimeGenerator();
fixture.Register(dtg.Next);
假设这API(通知一旦更多的方法小组的语法上):
public class DateTimeGenerator
{
public DateTime Next();
}