【问题标题】:Is defining TestMethod's in test base classes not supported by MsTest?MsTest 不支持在测试基类中定义 TestMethod 吗?
【发布时间】: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&lt;T&gt; : EqualsFixtureBase&lt;T&gt;),以测试这些运算符的实现不仅正确,而且与扩展定义一致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


【解决方案1】:

使用 VS 2010 我没有看到与您相同的行为。当我将你的 2 个类复制到一个测试项目中并编译它时,我得到了输出:

UTA004: Illegal use of attribute...The TestMethodAttribute can be 
defined only inside a class marked with the TestClass attribute

所以我标记了 EqualsFixutureBase

[TestClass]
public abstract class EqualsFixtureBase<T>
{
...
}

现在它在没有警告的情况下编译,当我为 ExampleOfAnEqualsTestFixture 选择运行测试时,它运行 Foo 和所有 5 个继承的 equals 测试。此外,当我复制 ExampleOfAnEqualsTestFixture 并将其用于 int 并运行解决方案的测试时,我看到示例字符串类和示例 int 类。

除了您的示例之外,您是否还在做一些可能导致您的问题的事情?

【讨论】:

  • 我没有得到你做的结果,没有编译器警告。我使用的是 VS2010 Ultimate,版本 10.0.30319.1。当我将 TestClassAttribute 添加到抽象基类时,TFS 发现继承的测试方法很好,竖起大拇指!但是本地VS测试运行器没有。也许您正在使用 resharper 在本地运行?这也适用于我,但是当我想在本地查看测试代码覆盖率时无济于事,因为我需要 VS 测试运行器。 ://
  • 我没有使用 Test Runner,我只是通过 IDE 手动启动测试或从命令提示符运行 MSTest。
  • 其实所有类型都在同一个项目中
【解决方案2】:

TestClassAttribute 允许您将方法放在抽象基础中。 IgnoreAttribute 将基类从测试列表中排除。在基类和标有TestClassAttribute, 的子类中,如果没有IgnoreAttribute 属性,则在基类中执行方法。

[TestClass][Ignore]
public abstract class EqualsFixtureBase<T>
{
....

【讨论】:

  • 使用 .NET Core 2.1 框架观察到的行为是,无论该类是否使用“Ignore”属性修饰,都不会尝试在抽象基类中进行测试。
  • @EdNoepel 抽象类的 [TestClass] 属性决定是否包含其方法 [Ignore] 属性
【解决方案3】:

开箱即用,看起来单元测试继承仅在基测试类与派生类位于同一程序集中时才有效。对我来说,这通常违背了拥有基类的目的。我也想知道为什么博客上没有更多关于此的帖子,以及我是否可能遗漏了什么。

您可以通过将基类链接到您要使用它的每个项目来解决该问题。也许将其标记为内部,这样多个副本就不会相互干扰。

您还可以扩展 TestClassExtensionAttribute 以挂钩到测试执行引擎。我尝试使用它来反映测试类并加载基类的测试,但是很多类没有记录,我无法让它工作。

【讨论】:

【解决方案4】:

如果将基类的程序集与派生的程序集放在同一个文件夹中,它会起作用吗? 也许这就是为什么将它们放在同一个组件中的原因;另一个程序集在他们想要的时候无法解析。我不确定在需要时如何建立您可能需要的正确探测路径。 .testsettings 可以表达诸如应用程序库和对运行程序的应用程序域的探测之类的内容,如果与根派生的单元测试程序集不同,也许正确设置的那些将有助于它绑定到基类程序集。

【讨论】:

  • 一切都在同一个程序集中。但是,如果他们不在,那又何必呢? CLR 仍然必须在执行任何操作之前加载所有类型,然后所有相关的类型元数据都可用
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多