【问题标题】:Testing Optional equivalence with FluentAssertions使用 FluentAssertions 测试可选等价性
【发布时间】:2021-01-03 06:06:20
【问题描述】:

我正在使用一个名为 Optional (https://github.com/nlkl/Optional) 的库,它允许函数式语言常见的“可能”抽象。

这个库很棒,但我在测试方面遇到了一个问题:我无法正确测试 2 个可选实例是否等效。

为了测试等效性,我使用了Fluent Assertions。但是,我没有得到想要的结果。

我会用代码说明问题:

#load "xunit"

[Fact]
void TestOptional()
{
    var a = new[] { 1, 2, 3 }.Some();
    var b = new[] { 1, 2, 3 }.Some();
    
    a.Should().BeEquivalentTo(b);
}

此测试失败,如屏幕截图所示(为方便起见,我使用的是 LINQPad)

如您所见,这不是人们所期望的。

我如何告诉 Fluent Assertions 使用 Option 类型正确检查等价性?

【问题讨论】:

  • 也许这会有所帮助:a.Should().BeEquivalentTo(b, opt => opt.ComparingByMembers<Option<int[]>>()); 不确定如何使其更通用并在全球范围内进行设置。
  • 另外,你的意思是{ 1, 2, 3 } = { 1, 3, 2 }而不是{ 1, 2, 3 } = { 1, 3, 3 }
  • @MichaelSchnerring 感谢您的关注 :) 已修复。

标签: c# unit-testing optional fluent-assertions maybe


【解决方案1】:

更新

I opened an issue on Github 关于您的问题和昨天的pull request was merged,因此下一个(预)版本应该能够让您优雅地解决您的问题:

新的重载允许您使用开放的泛型类型。 如果同时指定了开放类型和封闭类型,则封闭类型优先。

SelfReferenceEquivalencyAssertionOptions增加了以下方法:

  • public TSelf ComparingByMembers(System.Type type) { }
  • public TSelf ComparingByValue(System.Type type) { }

Here's the unit test 添加到 Fluent Assertions 中,展示了它的工作原理:

[Fact]
public void When_comparing_an_open_type_by_members_it_should_succeed()
{
    // Arrange
    var subject = new Option<int[]>(new[] { 1, 3, 2 });
    var expected = new Option<int[]>(new[] { 1, 2, 3 });

    // Act
    Action act = () => subject.Should().BeEquivalentTo(expected, opt => opt
        .ComparingByMembers(typeof(Option<>)));

    // Assert
    act.Should().NotThrow();
}

Fluent Assertions - Object Graph Comparison 说:

值类型

确定 Fluent Assertions 是否应该递归到对象的 属性或字段,它需要了解哪些类型具有价值 语义以及应将哪些类型视为引用类型。这 默认行为是处理 覆盖 Object.Equals 的每种类型 作为一个被设计为具有价值语义的对象。很遗憾, 匿名类型和元组也覆盖了这个方法,但是因为我们 往往在等价比较中经常使用它们,我们总是 按属性比较它们。

您可以使用ComparingByValue&lt;T&gt;ComparingByMembers&lt;T&gt; 单个断言的选项

Option&lt;T&gt;struct 并覆盖 Equals,因此 Fluent Assertions 将 ab 与值语义进行比较。

Option&lt;T&gt; 像这样实现Equals

public bool Equals(Option<T> other)
{
  if (!this.hasValue && !other.hasValue)
    return true;
  return this.hasValue
    && other.hasValue 
    && EqualityComparer<T>.Default.Equals(this.value, other.value);
}

因此,int[] 通过引用进行比较,您的测试失败。

您可以为每个测试单独覆盖此行为,如 Guro Stron 所说:

a.Should().BeEquivalentTo(b, opt => opt.ComparingByMembers<Option<int[]>>());

或全局通过静态AssertionOptions 类:

AssertionOptions.AssertEquivalencyUsing(options => 
    options.ComparingByMembers<Option<int[]>>());

编辑:

对于您的情况,Fluent Assertions 需要一个支持未绑定泛型类型的 AssertEquivalencyUsing 覆盖:

AssertionOptions.AssertEquivalencyUsing(options => 
    options.ComparingByMembers(typeof(Option<>)));

不幸的是,不存在这样的覆盖。

a 提出的另一个解决方案是扩展方法。这是一个非常简单的实现:

public static class FluentAssertionsExtensions
{
    public static void BeEquivalentByMembers<TExpectation>(
        this ComparableTypeAssertions<TExpectation> actual,
        TExpectation expectation)
    {
        actual.BeEquivalentTo(
            expectation,
            options => options.ComparingByMembers<TExpectation>());
    }
}

【讨论】:

  • 谢谢,但这仍然不是理想的解决方案。这意味着我将不得不配置我想要处理的每种不同类型的集合。我的意思是,如果希望它与 List 一起使用,我需要为 List 显式添加上面的行,对吗?
  • 是的,您需要添加该行。在您的情况下,这可能是不受欢迎的,但在其他情况下则不是 - 没有理想的解决方案。
  • @SuperJMN 检查我的更新答案,了解我想出的另一个解决方案。
  • @SuperJMN 再次更新了我的帖子。昨天合并了一个 PR,增加了对开放泛型类型的支持。
猜你喜欢
  • 2022-04-27
  • 2014-12-05
  • 2011-12-15
  • 2021-12-17
  • 1970-01-01
  • 2013-05-04
  • 2021-04-10
  • 2013-10-03
  • 1970-01-01
相关资源
最近更新 更多