将[AutoFixture] SemanticComparison OfLikeness应用于序列/集合/数组/ IEnumerable

时间:2012-07-30 09:33:52

标签: .net unit-testing collections autofixture semantic-comparison

我们编写了一个如下所示的测试。此测试要求我们为Equal - 类创建了en CodeTableItem - 重载:

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 -function,单元测试仍然运行为绿色,但我们不测试所有字段。我们希望避免这种Equality污染(见Test Specific 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 个答案:

答案 0 :(得分:12)

为什么你不想这样做

我不认为从任何List<T>创建一个相似性可以做你想做的事。据我了解,您想比较两个列表的内容。这与比较两个列表不一样......

考虑Likeness的作用:它比较 property 值。 List<T>的哪些属性?

他们是

  • 容量
  • 计数

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

换句话说,如果你这样做了,那就:

expectedValutaListWithLikeness.ShouldEqual(actual.ToList());

在功能上与此相同:

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

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

你应该做什么

您可以使用“相似性”将每个元素相互比较。这样的事情应该有效:

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以来已经这么多年了,我记不起那种方法的怪癖......

答案 1 :(得分: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));

注意:

需要从包含动态生成的代理的expected实例开始(覆盖Equals)。

您可以找到有关此功能的更多信息here

答案 2 :(得分:3)

以下答案来自me asking myself a duplicate of this question,见下文

您可以使用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 );
}

由于来自@Nikos Baxevanis的提示,基于@Mark Seemann答案的一体化助手的闪亮短暂实施: -

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> Test Specific Equality的最简洁方法。

OOTB(我已经失去了对我学到的内容的参考),Ploeh.SemanticComparison Likeness版本高达2.12仅适用于单个项目。

将相同的技术应用于项目集合的最佳方法是什么(理想情况下是OOTB,但对于经过充分判断的扩展方法套件非常开放),以便于以可组合的方式表达包含嵌入对象的项目的相似性?

这真的是一个自我回答,所以我可以存储帮助者,并允许一个地方放置“你在Vnn这样做”应该相似提供序列支持将来,但它不会这是我第一次对AFflicted AFicionados的回答微妙感到惊讶

答案 3 :(得分:3)

我想让其他人有这个问题 - 使用Ruben的第二个代码示例,你要比较Calendar和Calendar.Holidays,同时自定义两者的比较:

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
   ));

在此示例中,您首先在Calendar对象上设置要排除的属性等。然后,您提供自定义EqualsWith实现来处理Holidays集合。假期=&gt;然后lambda允许您自定义子比较,就像父对象一样。只要你喜欢很多括号,你就可以继续嵌套。

答案 4 :(得分:1)

我不确定这个问题是否仍然相关,但您要找的是https://github.com/jmansar/SemanticComparisonExtensions

您可以使用.WithCollectionInnerLikeness()进行集合比较,它可以为您提供所需的内容。