在单元测试中比较类似的对象

4

如何比较两个相似的对象?

给定 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");
4个回答

2

当我想要比较特定属性时,有时会使用匿名类型来实现,代码如下:

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

这不依赖于任何特定的测试框架。它利用匿名类型自动生成的Equals()方法,在失败的情况下,自动生成的ToString()方法会给出两个对象的完整描述,这使得很容易找出问题所在。
您还可以查看Mark Seemann的Likeness类型:

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

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


2
我决定更详细地介绍 StriplingWarrior 提及的 Likeness。它作为一个nuget package可用。
这里有一个例子:
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; }
    }
}

正如你所看到的:

  • 它会自动比较具有相同名称的属性
  • 您可以指定是否应匹配不同名称的属性(这个功能实际上默认情况下相当丑陋,但是您可以编写扩展方法来封装它)
  • 您可以指定不比较某个属性

1
你可以在测试项目中编写扩展类/方法来实现所需的结果,这样就不会在生产代码库中添加不必要的逻辑。
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));

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

0

FlintlockDTO 实现 IEquatable<Flintlock> 可能是个不错的选择,具体实现如下:

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

(别忘了重写并正确实现GetHashCode。)
然后,您只需检查
flintlockDto.Equals(flintlock)

在您选择的单元测试框架中。


我会感到非常不舒服,让人们产生两个不同类型的对象是“相等”的印象。此外,这个签名并没有覆盖Equals(object)签名,所以它实际上不能与大多数测试框架的AreEqual()断言一起使用。为了保持平衡,你还需要在Flintlock类中实现相同的相等检查,反过来做吗? - StriplingWarrior
@StriplingWarrior:但这正是OP想要测试的内容。尽管它们是不同的类型,但它们代表着相同的信息-其中一个只是另一个的数据传输对象。 - Evan Mulawski
是的,但更改两个不同生产类的Equals()和GetHashCode()行为似乎是为了适应基本单元测试而进行的大量测试诱导损害。 - StriplingWarrior
在这种情况下重写Equals只会影响从FlintlockDTOFlintlock的比较,这正是OP在测试中试图断言的。这也更易于维护 - 没有硬编码的属性名称作为字符串,也没有在测试方法中进行单个属性检查。 - Evan Mulawski
2
允许你的测试以这种方式影响生产代码是一种反模式,被称为"Equality Pollution"。还有其他可组合的途径可以使此检查可维护,同时仍然保持你的等式检查test-specific - StriplingWarrior

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