C# · 12月 28, 2021

c# – AutoFixture重构

我开始使用AutoFixture @L_419_0@,因为我的单元测试被大量的数据设置blo肿.我花更多的时间来设置数据,而不是写我的单元测试.以下是我的初始单元测试的样例(来自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,18); var arrivalDate2 = new DateTime(2010,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,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,CarrierMovement>( (departure,arrivalTime) => new CarrierMovement(departureLoc,arrivalDateTime));

到这个:

fixture.Register<Location,CarrierMovement>( () => new CarrierMovement(departureLoc,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)

你可以改为刚刚注册载体运动:

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

这将导致AutoFixture自动解析计划正确.这种方法更可维护,因为它允许您在将来添加一个参数到Schedule构造函数,而不会中断测试(只要AutoFixture可以解析参数类型).

但是,在这种情况下,我们可以做得更好,因为我们并不真正使用carrierMovements变量进行注册.我们真正需要做的只是告诉AutoFixture如何创建IEnumerable& CarrierMovement的实例.如果你不关心数字50(你不应该),我们甚至可以使用方法组语法,如下所示:

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

注意缺少方法调用parantheses:我们正在注册一个Func,并且由于CreateMany< T>方法返回IEnumerable< T>类型引用负责其余的.

但是,这些都是细节.在更高层次上,您可能想考虑不注册CarrierMovement.假设这个构造函数:

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

自动修复应该能够自己弄清楚.

它将为每个出发位置和到达位置创建一个新的位置实例,但这与原始测试中手动完成的没有什么不同.

当涉及到时代,默认情况下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());

但是,由于你问,我暗示了Schedule的构造函数如下所示:

public Schedule(IList<CarrierMovement> carrierMovements)

而且我真的认为你应该重新考虑改变这个API来进行IEnumerable&Carriemovement>.从API设计角度来说,通过任何成员(包括构造函数)提供集合意味着允许成员修改集合(例如通过调用它的添加,删除和清除方法).这不是你从构造函数中期望的行为,所以不要这样做.

AutoFixture会自动为上述示例中的所有Location对象生成新值,但由于cpu的速度,DateTime的后续实例可能相同.

如果你想增加DateTimes,你可以编写一个小的类,每次调用返回的DateTime.我会把这个类的实现留给感兴趣的读者,但是你可以这样注册:

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

假设这个API(再次通知上面的方法组语法):

public class DateTimeGenerator{ public DateTime Next();}