【问题标题】:Applying [AutoFixture] SemanticComparison OfLikeness to sequences / collections / arrays / IEnumerable将 [AutoFixture] SemanticComparison OfLikeness 应用于序列/集合/数组/IEnumerable
【发布时间】:2012-07-30 09:33:52
【问题描述】:

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

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-function,这意味着如果我用一个更多字段扩展CodeTableItem-class,并且忘记扩展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 忽略某些字段?或者有没有其他方法可以解决这个问题?

【问题讨论】:

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


    【解决方案1】:

    为什么你不想那样做

    我不认为从任何List&lt;T&gt; 创建一个相似之处可以达到你想要的效果。据我了解,您想比较两个列表的 contents。这与比较两个列表不同......

    考虑 Likeness 的作用:它比较 属性 值。 List&lt;T&gt;的属性是什么?

    他们是

    • 容量
    • 计数

    正如 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 以来已经有很多年了,以至于我不记得该方法的怪癖了...

    【讨论】:

    • 非常感谢,马克,成功了!优雅!
    【解决方案2】:

    只需添加.Without(x =&gt; 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 实例开始。

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

    【讨论】:

      【解决方案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
         ));
      

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

      【讨论】:

      • +1 不错。嘿,那些友好的拥抱括号非常令人欣慰:)
      【解决方案4】:

      以下答案来自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 的测试中,我正在寻找最简洁的方法来管理数组的 Test Specific Equality/IEnumerable&lt;T&gt;/seq&lt;'T&gt;

      OOTB(我已经忘记了我在哪里了解到这一点),Ploeh.SemanticComparisonLikeness 版本高达 2.12 仅适用于单个项目。

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

      这确实是一个自我回答,所以如果 Likeness 将来提供序列支持,我可以隐藏助手并允许放置“你在 V nn 中这样做”的地方,但它不会这是我第一次对来自 AFflicted AFicionados 的微妙答案感到惊讶

      【讨论】:

        【解决方案5】:

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

        您可以使用 .WithCollectionInnerLikeness() 进行集合比较,它会得到您想要的。

        【讨论】:

          猜你喜欢
          • 2017-04-07
          • 2013-03-11
          • 2021-09-01
          • 2019-03-01
          • 2016-08-19
          • 1970-01-01
          • 1970-01-01
          • 2010-10-04
          • 2014-01-06
          相关资源
          最近更新 更多