比较单元测试中的相似对象

本文关键字:相似 对象 单元测试 比较 | 更新日期: 2023-09-27 18:03:20

比较两个相似对象的最佳方法是什么?

给定FlintlockDTOFlintlock:

public class FlintlockDTO
{
  public string GName { get; set; }
  public string SharedPropertyName { get; set; }
  ...
}

public class Flintlock
{
  public Flintlock(FlintlockDTO inflator)
  {
    this.GoodName = inflator.GName;
    this.SharedPropertyName = inflator.SharedPropertyName;
    ...
  }
  public string GoodName { get; private set; }
  public string SharedPropertyName { get; private set; }
  ...
}

两个类共享N个属性(例如SharedPropertyName),但在M个相同的属性上不同,但命名不同(例如GoodName ' GName)

像fluentassert这样的工具几乎可以做到这一点,如果属性名称匹配,在我看来这是可行的:
flintlockDto.ShouldBeEquivalentTo(flintlock);

是否有办法在fluentassert或任何其他工具中巧妙地做到这一点?

在理想的情况下,

flintlockDto.IsTheSameAs(flintlock).WhenMapping("GName","GoodName");

比较单元测试中的相似对象

我决定详细说明一下StriplingWarrior提到的相似性。它是一个nuget包。

下面是一个例子:

using NUnit.Framework;
using Ploeh.SemanticComparison;
using Ploeh.SemanticComparison.Fluent;
namespace Tests
{
    [TestFixture]
    class Tests2
    {
        [Test]
        public void ObjectsShuldEqual()
        {
            var flintlockDto = new FlintlockDTO()
            {
                GName = "name",
                AdditionalProperty = "whatever",
                SharedPropertyName = "prop name"
            };
            var flintlock = new Flintlock(flintlockDto);
            Likeness<Flintlock, FlintlockDTO> flintFlockDtoLikeness = flintlock
                .AsSource().OfLikeness<FlintlockDTO>()
                .With(dto => dto.GName).EqualsWhen((flintlock1, dto) => flintlock1.GoodName == dto.GName) // you can write an extension method to encapsulate it
                .Without(dto => dto.AdditionalProperty);
            // assert
            flintFlockDtoLikeness.ShouldEqual(flintlockDto);
        }
    }
    public class FlintlockDTO
    {
        public string GName { get; set; }
        public string SharedPropertyName { get; set; }
        public string AdditionalProperty { get; set; }
    }
    public class Flintlock
    {
        public Flintlock(FlintlockDTO inflator)
        {
            this.GoodName = inflator.GName;
            this.SharedPropertyName = inflator.SharedPropertyName;
        }
        public string GoodName { get; private set; }
        public string SharedPropertyName { get; private set; }
    }
}

如你所见:

  • 它对同名的属性执行自动比较
  • 你可以指定不同名称的属性是否应该匹配(这个实际上是相当丑陋的开箱,但你可以写一个扩展方法来封装它)
  • 你可以指定一个属性是否不应该被比较

当我想将特定的属性放在一起比较时,我有时使用的一种策略是利用匿名类型,如下所示:

Assert.AreEqual(
    new{flintlockDto.GoodName, flintlockDto.SharedPropertyName},
    new{GoodName = flintlock.GName, flintlock.SharedPropertyName});

这并不依赖于任何特定的测试框架。它利用匿名类型的自动生成的Equals()方法,在失败的情况下,自动生成的ToString()方法为您提供了两个对象的完整描述,这使您很容易找出哪里出了问题。

你可能还想看看Mark Seemann的肖像类型:

我们如何在不引入平等污染的情况下解决这个难题?AutoFixture以通用的Likeness类的形式提供了一种选择。该类提供了从TSource到TDestination的基于约定的测试特定的相等映射,并覆盖Equals方法。

…可以自定义比较来覆盖某些属性的行为…

您可以在您的测试项目中编写一个扩展类/方法并获得您想要的结果,并且您不会用不必要的逻辑污染您的生产代码库。

static class Extensions
{
  public static bool IsEqualTo(this FlintlockDTO expected, Flintlock actual) 
  {
    return expected.GName == actual.GoodName && expected.SharedPropertyName == actual.SharedPropertyName;
  }
}

在您的测试中,您将能够运行以下命令:

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

和测试逻辑在您的生产代码库中不可用。

FlintlockDTO实现IEquatable<Flintlock>可能是好的:

public override Equals(Flintlock other)
{
    return GName == other.GoodName; // modify as necessary (case, culture, etc.)
}

(不要忘记重写并正确实现GetHashCode)

然后,你可以检查是否

flintlockDto.Equals(flintlock)