我开始使用AutoFixture http://autofixture.codeplex.com/,因为我的单元测试由于大量数据设置而变得臃肿。我把更多的时间花在设置数据上,而不是写单元测试上。下面是我的初始单元测试的示例(示例取自DDD blue book中的cargo应用程序示例)
[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)));
}我想知道是否有更好的方法来重构它。想要做得更短更容易。
发布于 2010-04-12 21:54:53
你最初的尝试看起来不错,但至少有几件事你可以简化一点。
首先,你应该能够减少这个:
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 (你不应该),我们甚至可以像这样使用Method Group语法:
fixture.Register(fixture.CreateMany<CarrierMovement>);注意缺少方法调用的单行句:我们注册了一个IEnumerable<T>,并且由于CreateMany<T>方法返回了Func,所以类型推断会处理剩下的事情。
然而,这些都是细节。在更高的层次上,您可能希望考虑根本不注册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实例可能是相同的。
如果你想增加DateTimes,你可以编写一个小的类,在每次调用时递增返回的DateTime。我将把该类的实现留给感兴趣的读者,但是您可以像这样注册它:
var dtg = new DateTimeGenerator();
fixture.Register(dtg.Next);假设此API (再次注意上面的Method Group语法):
public class DateTimeGenerator
{
public DateTime Next();
}https://stackoverflow.com/questions/2622334
复制相似问题