将 [AutoFixture] 的语义比较 OfLikeness 应用到序列/集合/数组/IEnumerable

14
我们编写了一个类似下面这样的测试。该测试要求我们为CodeTableItem类创建一个Equal重载:
ICollection<CodeTableItem> expectedValutaList = new List<CodeTableItem>();
expectedValutaList.Add(new CodeTableItem("DKK", "DKK"));
expectedValutaList.Add(new CodeTableItem("EUR", "EUR"));
RepoDac target = new RepoDac(); 

var actual = target.GetValutaKd();

CollectionAssert.AreEqual(expectedValutaList.ToList(),actual.ToList());

测试工作正常,但不幸的是它依赖于Equality函数,这意味着如果我扩展CodeTableItem类加上一个字段,并忘记扩展Equals函数,单元测试仍然可以通过,尽管我们没有测试所有字段。我们想避免这种Equality污染(参见测试特定的相等性),它仅被编写以符合测试。

我们尝试使用OfLikeness,并以以下方式重写了测试:

ICollection<CodeTableItem> expectedValutaList = new List<CodeTableItem>();
expectedValutaList.Add(new CodeTableItem("DKK", "DKK"));
expectedValutaList.Add(new CodeTableItem("EUR", "EUR"));
var expectedValutaListWithLikeness = 
          expectedValutaList.AsSource().OfLikeness<List<CodeTableItem>>();

RepoDac target = new RepoDac(); 
ICollection<CodeTableItem> actual;

actual = target.GetValutaKd();

expectedValutaListWithLikeness.ShouldEqual(actual.ToList());

但是测试失败了,因为Capacity不相等。我编写了一个通过反射运行多次的代码,并通常最终实现了忽略字段的重载。是否有一种方法可以使用OfLikenessShouldEqual来忽略特定字段?或者还有其他解决此问题的方式吗?

5个回答

12

为什么你不想那样做

我认为从任何List<T>创建一个Likeness并不会做到您想要的。据我所知,您想要比较两个列表的内容。这与比较两个列表并不相同...

考虑一下Likeness所做的事情:它比较属性值。 List<T>的属性是什么?

它们是:

  • 容量(Capacity)
  • 计数(Count)

正如Nikos Baxevanis在他的答案中指出的那样,您可以使用Without方法忽略Capacity属性的值,但这意味着只剩下Count属性。

换句话说,如果您这样做了,那么这个:

expectedValutaListWithLikeness.ShouldEqual(actual.ToList());

这将是功能上等同于:

Assert.AreEqual(expected.Count, actual.Count)

换句话说,这些列表可能具有完全不同的数据,但如果每个列表仅具有相同数量的元素,则测试仍将通过。那可能不是你想要的...

你应该怎么做

你可以使用Likeness来将每个元素相互比较。类似这样的东西应该可以工作:

var expectedValutaList = new List<CodeTableItem>();
expectedValutaList.Add(new CodeTableItem("DKK", "DKK"));
expectedValutaList.Add(new CodeTableItem("EUR", "EUR"));

var expectedValutaListWithLikeness = from cti in expectedValutaList
                                     select cti
                                         .AsSource()
                                         .OfLikeness<CodeTableItem>();

var target = new RepoDac(); 

var actual = target.GetValutaKd();

Assert.IsTrue(expectedValutaListWithLikeness.Cast<object>().SequenceEqual(
    actual.Cast<object>()));

您也可以使用CollectionAssert进行断言,但自从我上次使用MSTest以来已经过了这么多年,我不能记住该方法的细节...


8
只需添加.Without(x => x.Capacity),Likeness实例将在比较值时忽略Capacity属性。
var expectedValutaListWithLikeness = 
      expectedValutaList.AsSource().OfLikeness<List<CodeTableItem>>()
      .Without(x => x.Capacity);

更新:

正如Mark Seemann在他的回答中指出的那样,你可能想要将每个元素相互比较。以下是一种稍微不同的方式,允许您执行非常灵活的比较。

假设RepoDac类返回类似以下内容:

public class RepoDac
{
    public ICollection<CodeTableItem> GetValutaKd()
    {
        return new[]
        {
            new CodeTableItem("DKK", "DKK"),
            new CodeTableItem("EUR", "EUR")
        };
    }
}

对于expectedValutaList上的每个实例,您可以创建一个动态代理,使用Likeness覆盖Equals方法:
var object1 = new CodeTableItem("DKK", "DKK1")
    .AsSource().OfLikeness<CodeTableItem>()
    .Without(x => x.Property2)
    .CreateProxy();

var object2 = new CodeTableItem("EUR2", "EUR")
    .AsSource().OfLikeness<CodeTableItem>()
    .Without(x => x.Property1)
    .CreateProxy();

注意,object1和object2有不同的动态生成Equals方法。(第一个忽略Property2而第二个忽略Property1。) 下面的测试通过:
var expected = new List<CodeTableItem>();
expected.Add(object1);
expected.Add(object2);

var target = new RepoDac();
var actual = target.GetValutaKd();

Assert.IsTrue(expected.SequenceEqual(actual));

注意:

必须使用包含动态生成代理(覆盖Equals方法)的expected实例来开始。

您可以在此处找到有关此功能的更多信息。


3
以下答案源自我自己在重复提出此问题时的回答,请参见以下内容。
您可以使用“SequenceLike”操作,该操作暗示了LINQ的“SequenceEqual”运算符。
这使得我们可以编写以下代码:-
[Theory, AutoData]
public void ShouldMap(  Dto inputDto )
{
    var mapped = inputDto.ToModel();

    inputDto.AsSource().OfLikeness<Model>()
        .Without( x => x.IgnorableProperty )
        .With( x => x.Tags ).EqualsWhen( ( dto, model ) => 
            model.Tags.SequenceLike( dto.Tags ) )
        .ShouldEqual( mapped );
}

基于@Mark Seemann的答案,感谢@Nikos Baxevanis的推动,这是一个闪亮的简短实现,可以作为集成辅助工具:

static class LikenessSequenceExtensions
{
    public static bool SequenceLike<T, TSource>( this IEnumerable<T> that, IEnumerable<TSource> source )
    {
        return SequenceLike<T, TSource>( that, source, x => x );
    }

    public static bool SequenceLike<T, TSource>( this IEnumerable<T> that, IEnumerable<TSource> source, Func<Likeness<TSource, T>, IEquatable<T>> customizeLikeness )
    {
        return source.Select( x => customizeLikeness( x.AsSource().OfLikeness<T>() ) ).SequenceEqual( that.Cast<object>() );
    }
}

我的原始实现:

static class LikenessSequenceExtensions0
{
    public static bool SequenceLike0<T, TSource>( this T[] that, TSource[] source )
    {
        return source.SequenceLike0( that, likeness => likeness );
    }

    public static bool SequenceLike0<T, TSource>( this T[] that, TSource[] source, Func<Likeness<TSource, T>, IEquatable<T>> customizeLikeness )
    {
        return source.SequenceEqual( that, ( x, y ) => customizeLikeness( x.AsSource().OfLikeness<T>() ).Equals( y ) );
    }

    public static bool SequenceEqual<T, TSource>( this T[] that, TSource[] source, Func<T, TSource, bool> equals )
    {
        return that.Length == source.Length && that.Zip( source, Tuple.Create ).All( x => equals( x.Item1, x.Item2 ) );
    }
}

原始重复问题

我正在寻找管理xunit.AutoFixture测试中数组/ IEnumerable<T> / seq<'T>的最简洁方法,以实现测试特定等式

默认情况下(我已经忘了在哪里学到这个),Ploeh.SemanticComparison的版本Likeness只适用于各个项目。

最佳方法是将相同的技术应用于项目集合(理想情况下是默认提供但非常开放地面向扩展方法套件),以便以可组合的方式表达包含嵌入对象的项目的相似之处?

这确实是一个自我回答,所以我可以存放助手并允许放置“您可以在V n.n中以这种方式执行”应该在未来提供序列支持的情况下,但这不会是我第一次对AFflicted AFicionados可能的答案的微妙性感到惊讶


3

我希望为其他遇到此问题的人明确这一点-使用Ruben的第二个代码示例,当您想要比较日历和日历节假日,并同时定制两者的比较时:

var expectedCalendar = newCalendar.AsSource()
   .OfLikeness<Calendar>()
   .Without(c=>c.Id) //guid, will never be equal
   .With(c=>c.Holidays).EqualsWhen((source, dest) => 
      source.Holidays.SequenceLike(dest.Holidays, holiday => 
          holiday.Without(h=>h.SecondsUntil) //changes every second
   ));

在这个例子中,您首先在日历对象上设置要排除等属性。然后,您提供一个自定义的EqualsWith实现来处理Holidays集合。 holiday=> lambda允许您像父级一样自定义子级比较。只要您喜欢很多括号,就可以继续嵌套。

+1 很好。嘿,那些友好的括号真是令人感到安慰 :) - Ruben Bartelink

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接