简体   繁体   English

使用 FluentAssertions 测试可选的等效性

[英]Testing Optional equivalence with FluentAssertions

I'm using a library called Optional ( https://github.com/nlkl/Optional ) that allows the "maybe" abstraction that is common to functional languages.我正在使用一个名为Optional ( https://github.com/nlkl/Optional ) 的库,它允许函数式语言常见的“可能”抽象。

The library is awesome, but I'm facing a problem regarding testing: I can't test correctly whether 2 optional instances are equivalent or not.该库很棒,但我在测试方面面临一个问题:我无法正确测试 2 个可选实例是否等效。

To test for equivalence I'm using Fluent Assertions .为了测试等效性,我使用Fluent Assertions However, I'm not getting the desired results.但是,我没有得到想要的结果。

I will illustrate the problem with code:我将用代码说明问题:

#load "xunit"

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

This test fails, as I show in the screenshot (I'm using LINQPad, for convenience)这个测试失败了,正如我在屏幕截图中显示的(为了方便,我正在使用 LINQPad)

在此处输入图片说明

As you see, this isn't what one expects.如您所见,这不是人们所期望的。

How do I tell Fluent Assertions to check the equivalence correctly using the Option type?我如何告诉 Fluent Assertions 使用 Option 类型正确检查等效性?

UPDATE更新

I opened an issue on Github regarding your problem and yesterday a pull request was merged , so the next (pre-)release should enable you to solve your problem elegantly: 我在 Github 上打开了一个关于你的问题的问题,昨天 合并了一个 拉取请求,所以下一个(预)发布应该能让你优雅地解决你的问题:

The new overloads allows you to use an open generic type.新的重载允许您使用开放的泛型类型。 If both an open and closed type are specified, the closed type takes precedence.如果同时指定了开放类型和封闭类型,则封闭类型优先。

SelfReferenceEquivalencyAssertionOptions adds the following methods: SelfReferenceEquivalencyAssertionOptions添加了以下方法:

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

Here's the unit test that was added to Fluent Assertions showing how it works: 是添加到 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 says: Fluent Assertions - 对象图比较说:

Value Types值类型

To determine whether Fluent Assertions should recurs into an object's properties or fields, it needs to understand what types have value semantics and what types should be treated as reference types.要确定 Fluent Assertions 是否应该递归到对象的属性或字段中,它需要了解哪些类型具有值语义以及哪些类型应该被视为引用类型。 The default behavior is to treat every type that overrides Object.Equals as an object that was designed to have value semantics .默认行为是将覆盖 Object.Equals 的每个类型视为旨在具有值语义的对象 Unfortunately, anonymous types and tuples also override this method, but because we tend to use them quite often in equivalency comparison, we always compare them by their properties.不幸的是,匿名类型和元组也覆盖了这个方法,但是因为我们倾向于在等价比较中经常使用它们,所以我们总是通过它们的属性来比较它们。

You can easily override this by using the ComparingByValue<T> or ComparingByMembers<T> options for individual assertions您可以通过为各个断言使用ComparingByValue<T>ComparingByMembers<T>选项轻松覆盖它

Option<T> is a struct and overrides Equals , so Fluent Assertions compares a and b with value semantics. Option<T>是一个struct并覆盖Equals ,因此 Fluent Assertions 将ab与值语义进行比较。

Option<T> implements Equals like this: Option<T>像这样实现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);
}

Thus int[] is compared by reference and your test fails.因此int[]通过引用进行比较并且您的测试失败。

You can override this behavior for each test individually, like Guro Stron said:您可以为每个测试单独覆盖此行为,就像Guro Stron所说:

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

Or globally via static AssertionOptions class:或者通过静态AssertionOptions类全局:

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

edit:编辑:

For your case Fluent Assertions would need an AssertEquivalencyUsing override that supports unbound generic types:对于您的情况,Fluent Assertions 需要一个支持未绑定泛型类型的AssertEquivalencyUsing覆盖:

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

No such override exists, unfortunately.不幸的是,不存在这样的覆盖。

Another solution a came up with would be an extension method.提出的另一个解决方案是扩展方法。 Here a very simplistic implementation:这是一个非常简单的实现:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM