草庐IT

c# - AutoFixture重构

coder 2024-06-02 原文

我开始使用AutoFixture http://autofixture.codeplex.com/因为我的单元测试因大量数据设置而变得臃肿。与编写单元测试相比,我花在设置数据上的时间更多。这是我的初始单元测试的示例(示例取自 DDD 蓝皮书的 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)));
}

我想知道是否有更好的方法来重构它。想做得更短更容易。

最佳答案

您的初步尝试看起来不错,但至少有几件事您可以稍微简化。

首先,你应该能够减少这个:

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。

但是,假设 Schedule 构造函数如下所示:

public Schedule(IEnumerable<CarrierMovement> carrierMovements)

您可以直接注册 carrierMovements像这样:

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

这会导致 AutoFixture 自动正确解析 Schedule。这种方法更易于维护,因为它允许您在将来向 Schedule 构造函数添加参数而不会破坏测试(只要 AutoFixture 可以解析参数类型)。

但是,在这种情况下我们可以做得更好,因为我们并没有真正使用 carrierMovements除了注册之外的任何变量。我们真正需要做的只是告诉 AutoFixture 如何创建 IEnumerable<CarrierMovement> 的实例。 .如果您不关心数字 50(您不应该关心),我们甚至可以像这样使用 Method Group 语法:

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

注意缺少方法调用括号:我们正在注册一个 Func,并且由于 CreateMany<T>方法返回 IEnumerable<T>类型推断负责其余的工作。

不过,这些都是细节。在更高的层次上,您可能想要考虑根本不注册 CarrierMovement。假设这个构造函数:

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

autofixture 应该能够自己解决。

它将为每个 departmentLocation 和 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());

但是,既然你问了,我的意思是 Schedule 构造函数看起来像这样:

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(再次注意上面的方法组语法):

public class DateTimeGenerator
{
    public DateTime Next();
}

关于c# - AutoFixture重构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2622334/

有关c# - AutoFixture重构的更多相关文章

  1. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  2. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  3. ruby-on-rails - 如何重构 "shared"方法? - 2

    我正在使用RubyonRails3.2.2,我想从我的模型/类中“提取”一些方法。也就是说,在不止一个类/模型中,我有一些方法(注意:方法与用户授权相关,并被命名为“CRUD方式”),这些方法实际上是相同的;所以我认为DRY方法是将这些方法放在“共享”模块或类似的东西中。实现该目标的常见且正确的方法是什么?例如,我应该将“共享”代码放在哪里(在哪些目录和文件中)?如何在我的类/模型中包含提到的方法?你有什么建议?注意:我正在寻找“RubyonRails制作东西的方式”。 最佳答案 一种流行的方法是使用ActiveSupport关注点

  4. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  5. ruby-on-rails - 在 haml View 中重构条件 - 2

    除了可访问性标准不鼓励使用这一事实指向当前页面的链接,我应该怎么做重构以下View代码?#navigation%ul.tabbed-ifcurrent_page?(new_profile_path)%li{:class=>"current_page_item"}=link_tot("new_profile"),new_profile_path-else%li=link_tot("new_profile"),new_profile_path-ifcurrent_page?(profiles_path)%li{:class=>"current_page_item"}=link_tot("p

  6. ruby - 需要重构为新的 Ruby 1.9 哈希语法 - 2

    这个问题在这里已经有了答案:HashsyntaxinRuby[duplicate](1个回答)关闭5年前。我有一个Recipe,其中包含以下未通过lint测试的代码:service'apache'dosupports:status=>true,:restart=>true,:reload=>trueend失败并出现错误:UsethenewRuby1.9hashsyntax.supports:status=>true,:restart=>true,:reload=>true不确定新语法是什么样的...有人可以帮忙吗?

  7. ruby - 重构条件变量赋值 - 2

    我正在做一个项目。目前我有一个相当大的条件语句,它根据一些输入参数为变量赋值。所以,我有这样的东西。ifsomeconditionx=somevalueelsifanotherconditionx=adifferentvalue...重构它的最佳方法是什么?我希望我最终会得到类似的东西x=somevalueifsomecondition||anothervalueifanothercondition这种事情有规律吗? 最佳答案 只需将赋值放在if之外即可。x=ifsomeconditionsomevalueelsifanotherc

  8. c# - C# 中的 Flatten Ruby 方法 - 2

    我如何做Ruby方法"Flatten"RubyMethod在C#中。此方法将锯齿状数组展平为一维数组。例如:s=[1,2,3]#=>[1,2,3]t=[4,5,6,[7,8]]#=>[4,5,6,[7,8]]a=[s,t,9,10]#=>[[1,2,3],[4,5,6,[7,8]],9,10]a.flatten#=>[1,2,3,4,5,6,7,8,9,10 最佳答案 递归解决方案:IEnumerableFlatten(IEnumerablearray){foreach(variteminarray){if(itemisIEnume

  9. ruby - 可以像在 C# 中使用#region 一样在 Ruby 中使用 begin/end 吗? - 2

    我最近从C#转向了Ruby,我发现自己无法制作可折叠的标记代码区域。我只是想到做这种事情应该没问题:classExamplebegin#agroupofmethodsdefmethod1..enddefmethod2..endenddefmethod3..endend...但是这样做真的可以吗?method1和method2最终与method3是同一种东西吗?还是有一些我还没有见过的用于执行此操作的Ruby惯用语? 最佳答案 正如其他人所说,这不会改变方法定义。但是,如果要标记方法组,为什么不使用Ruby语义来标记它们呢?您可以使用

  10. c# - Ruby 等效于 C# Linq 聚合方法 - 2

    什么是Linq聚合方法的ruby​​等价物。它的工作原理是这样的varfactorial=new[]{1,2,3,4,5}.Aggregate((acc,i)=>acc*i);每次将数组序列中的值传递给lambda时,变量acc都会累积。 最佳答案 这在数学以及几乎所有编程语言中通常称为折叠。它是更普遍的变形概念的一个实例。Ruby从Smalltalk中继承了这个特性的名称,它被称为inject:into:(像aCollectioninject:aStartValueinto:aBlock一样使用。)所以,在Ruby中,它称为inj

随机推荐