【问题标题】:Should I be using IEquatable to ease testing of factories?我应该使用 IEquatable 来简化对工厂的测试吗?
【发布时间】:2016-03-03 11:48:12
【问题描述】:

我经常使用代表工厂生产的实体的类。 为了轻松测试我的工厂,我通常实现IEquatable<T>,同时覆盖GetHashCodeEquals(如MSDN 所建议的那样)。

例如;以以下简化的实体类为例。通常我的课程有更多的属性。偶尔也有一个集合,在Equals 方法中我使用SequenceEqual 进行检查。

public class Product : IEquatable<Product>
{
    public string Name
    {
        get;
        private set;
    }

    public Product(string name)
    {
        Name = name;
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }

        Product product = obj as Product;

        if (product == null)
        {
            return false;
        }
        else
        {
            return Equals(product);
        }
    }

    public bool Equals(Product other)
    {
        return Name == other.Name;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();
    }
}

这意味着我可以像这样进行简单的单元测试(假设构造函数在其他地方进行了测试)。

[TestMethod]
public void TestFactory()
{
    Product expected = new Product("James");

    Product actual = ProductFactory.BuildJames();

    Assert.AreEqual(expected, actual);
}

然而,这引发了一些问题。

  1. GetHashCode 并未实际使用,但我已花时间实现它。
  2. 除了测试之外,我很少真正想在我的实际应用程序中使用Equals
  3. 我花更多时间编写更多测试以确保 Equals 实际正常工作。
  4. 我现在要维护另外三种方法,例如向类添加属性,更新方法。

但是,这确实给了我一个非常整洁的TestMethod

这是对IEquatable 的适当使用,还是我应该采取其他方法?

【问题讨论】:

  • 使用 IEquatable 肯定是不合适的,因为对于一个产品来说不只有一个相等的概念。但是您可以使用 Resharper 也可以为您生成的 IEqualityComparer。是否测试这样一个生成的比较器是一个选择。
  • GetHashCode 基于可变字段是个坏主意blogs.msdn.com/b/ericlippert/archive/2011/02/28/…

标签: c# unit-testing factory-pattern iequatable


【解决方案1】:

这是否是一个好主意实际上取决于您的工厂创建哪种类型。有两种类型:

  • 具有值语义的类型(简称值类型)和

  • 具有引用语义的类型(简称引用类型。)

在 C# 中,通常将 struct 用于值类型,将 class 用于引用类型,但您不必这样做,您可以同时使用 class。重点是:

  • 值类型是小的,通常是不可变的,自包含的对象,其主要目的是包含某个值,而

  • 引用类型是具有复杂可变状态的对象,可能对其他对象的引用,以及非平凡的功能,即算法、业务逻辑等。

如果您的工厂正在创建一个值类型,那么当然,请继续使用IEquatable 并使用这个巧妙的技巧。但是在大多数情况下,我们不会将工厂用于值类型,这往往相当琐碎,我们将工厂用于引用类型,这往往相当复杂,所以如果你的工厂正在创建引用类型,那么真的,这些各种对象仅用于通过引用进行比较,因此添加Equals()GetHashCode() 方法是从误导到错误的任何地方。

从哈希映射发生的事情中得到一个提示:在一个类型中出现Equals()GetHashCode() 通常意味着您可以使用这种类型的实例作为哈希映射中的键;但是如果对象不是不可变的值类型,那么它的状态可能会在它被放入映射后发生变化,在这种情况下,GetHashCode() 方法将开始评估其他内容,但哈希映射永远不会打扰重新调用GetHashCode() 以便在地图中重新定位对象。这种情况下的结果往往是混乱的。

因此,底线是,如果您的工厂创建了复杂的对象,那么您或许应该采取不同的方法。显而易见的解决方案是调用工厂,然后检查返回对象的每个属性以确保它们都符合预期。

我也许可以提出对此的改进,但请注意,我只是想到了它,我从未尝试过,因此在实践中它可能会也可能不会成为一个好主意。这里是:

您的工厂可能会创建实现特定接口的对象。 (否则,拥有工厂有什么意义,对吗?)因此,理论上您可以规定新创建的实现此接口的对象实例应具有初始化为特定值集的某些属性。这将是接口强加的规则,因此您可以将一些函数绑定到接口以检查这是否为真,并且该函数甚至可以通过一些提示进行参数化,以在不同情况下期望不同的初始值。

(上次我检查过,在 C# 中,绑定到接口的方法通常是 扩展方法;我不记得 C# 是否允许静态方法成为一部分接口,或者 C# 的设计者是否在语言中添加了与 Java 的默认接口方法一样简洁优雅的东西。)

因此,使用扩展方法,它可能看起来像这样:

public boolean IsProperlyInitializedInstance( this IProduct self, String hint )
{
    if( self.Name != hint )
        return false;
    //more checks here
    return true;
}

IProduct product = productFactory.BuildJames();
Assert.IsTrue( product.IsProperlyInitializedInstance( hint:"James" ) );

【讨论】:

    【解决方案2】:

    对于测试代码,您可以使用基于反射的相等性,例如:Comparing object properties in c#

    许多测试库都提供了这样的实用程序,这样您就不必更改生产代码的设计以适应测试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-11
      • 1970-01-01
      • 2023-03-20
      • 2016-07-23
      • 1970-01-01
      • 2014-09-27
      相关资源
      最近更新 更多