【发布时间】:2010-08-19 16:16:54
【问题描述】:
此问题涉及一种通用单元测试技术,该技术可能非常有用,适用范围广泛。但是用一个例子来更好地说明我的问题更容易理解。
假设我想测试所有覆盖Equals() 的类型是否正确。由于Equals() 在System.Object 中被定义为虚拟,因此多种类型可能会改变这种行为。这样做的每种类型都必须进行测试,以确保新行为遵循该方法调用者的隐含期望。特别是对于Equals(),如果您重写该方法,则新实现必须确保两个相等的对象也具有相等的哈希码,如System.Object.GetHashCode() 所定义。
因此,为了强制执行这一点,将需要多个测试类,并且它们都将测试所有这些类型的行为的相同一致性。
为了避免重新键入测试此类所需的所有 TestMethod,我改为定义一个如下所示的基本测试类,并让这些测试类都继承相同的行为测试套件:
/// <summary>
/// Test fixture base class for testing types that overrides Object.Equals()
/// </summary>
/// <typeparam name="T">The production type under test</typeparam>
public abstract class EqualsFixtureBase<T>
{
#region Equals tests
protected static void CompareInstances(T inst1, T inst2, bool expectedEquals)
{
Assert.AreEqual(expectedEquals, inst1.Equals((T)inst2));
Assert.AreEqual(expectedEquals, inst1.Equals((object)inst2));
if (expectedEquals)
{
// equal instances MUST have identical hash codes
// this is a part of the .NET Equals contract
Assert.AreEqual(inst1.GetHashCode(), inst2.GetHashCode());
}
else
{
if (inst2 != null)
{
Assert.AreNotEqual(inst1.GetHashCode(), inst2.GetHashCode());
}
}
}
/// <summary>
/// Creates version 1 instance of the type under test, not 'Equal' to instance 2.
/// </summary>
/// <returns>An instance created with properties 1.</returns>
protected abstract T CreateInstance1();
/// <summary>
/// Creates version 2 instance of the type under test, not 'Equal' to instance 1.
/// </summary>
/// <returns>An instance created with properties 2.</returns>
protected abstract T CreateInstance2();
/// <summary>
/// Creates an instance equal to the version 1 instance, but not the identical
/// same object.
/// </summary>
/// <returns>An instance created with properties equal to instance 1.</returns>
protected abstract T CreateInstanceThatEqualsInstance1();
[TestMethod]
public void Equals_NullOrDefaultValueTypeInstance()
{
T instance = CreateInstance1();
CompareInstances(instance, default(T), false);
}
[TestMethod]
public void Equals_InstanceOfAnotherType()
{
T instance = CreateInstance1();
Assert.IsFalse(instance.Equals(new object()));
}
[TestMethod]
public void Equals_SameInstance()
{
T slot1 = CreateInstance1();
CompareInstances(slot1, slot1, true);
}
[TestMethod]
public void Equals_EqualInstances()
{
T slot1 = CreateInstance1();
T slot2 = CreateInstanceThatEqualsInstance1();
CompareInstances(slot1, slot2, true);
CompareInstances(slot2, slot1, true);
}
[TestMethod]
public void Equals_NonEqualInstances()
{
T slot1 = CreateInstance1();
T slot2 = CreateInstance2();
CompareInstances(slot1, slot2, false);
CompareInstances(slot2, slot1, false);
}
#endregion Equals tests
}
然后我可以为覆盖 Equals() 的每种类型重用这些测试方法。例如,这将是用于测试 System.String 类型是否正确实现 Equals() 的测试类定义。
[TestClass]
public class ExampleOfAnEqualsTestFixture : EqualsFixtureBase<string>
{
[TestMethod]
public void Foo()
{
Assert.IsTrue(true);
}
protected override string CreateInstance1()
{
return "FirstString";
}
protected override string CreateInstance2()
{
return "SecondString";
}
protected override string CreateInstanceThatEqualsInstance1()
{
return "FirstString";
}
}
这也可以进一步扩展。例如,对于重载 == 和 != 运算符的类型,可以定义第二个抽象测试基类(即EqualsOperatorsFixtureBase<T> : EqualsFixtureBase<T>),以测试这些运算符的实现不仅正确,而且与扩展定义一致Equals() 和 GetHashCode()。
我可以使用 NUnit 做到这一点,但使用 MsTest 时会出现问题。
a) Visual Studio 2010 仅发现 Foo() 测试方法,而不是继承的测试方法,因此无法运行它们。似乎 Visual Studio 测试加载器没有遍历测试类的继承层次结构。
b) 当我在 TFS 中签入这些类型时,TFS 找到抽象的 EqualsFixtureBase 类型并认为它是要运行的测试类。但由于无法创建它,因此无法运行它并将该类型的测试标记为不确定 - 这会导致测试运行失败,因此构建失败(!)。
有没有办法解决这个问题,或者这是 MsTest 和 Visual Studio 的限制?
如果是这样,是否在 VS/TFS 的路线图中解决了这个问题??
这将非常有用,尤其是在测试实现接口或属于继承层次结构的生产类型时,其中某些成员具有语义“合同类型”属性或不变量 - 如果有意义的话。
基本上,没有对此的支持会阻止我重构我的测试代码以消除重复。
谢谢
编辑:我在其中一个 MSDN 博客上找到了 this link,上面写着以下内容
“在 Whidbey 中,缺少对测试类继承的支持。在 Nunit 中,完全支持。这将在 Orcas 中得到纠正。”
那是三年前写的。为什么这个还没有添加?我不明白,这样做是有正当理由的,在我看来,这将是一个微小的变化。还是我只是没有在这里跳正确的篮球?
【问题讨论】:
-
@Jack & Rory:赏金期即将结束,而这个问题在很大程度上仍未得到解决,所以我不会将此问题标记为已回答。你仍然做出了很好的贡献,并且都有部分帮助,所以我对你各自的答案都投了一个票。如果我正确理解规则,这意味着您将分享赏金。
标签: unit-testing mstest