控制Visual Studio中单元测试的执行顺序

时间:2013-12-20 20:16:51

标签: c# unit-testing visual-studio-2012

好的,我已经完成了关于此的好消息。 我有一系列单元测试,它们调用一个静态类,一旦初始化,就会设置不能(或我不希望)改变的属性。

我的问题是我无法强制执行测试运行的设定顺序。如果可以的话,我可以运行它们,因为静态属性将以可靠的方式设置,我可以断言它们,但不幸的是,Microsoft.VisualStudio.TestTools.UnitTesting框架只是以看似随机的顺序运行它们

所以,我发现这个http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.priorityattribute.aspx在备注部分中说“测试系统没有使用此属性。它是为了自定义目的而提供给用户的。”咦?那有什么用呢?他们是否希望我编写自己的测试包装器来利用这个神奇的属性(如果我想要达到那种程度的努力,我可以轻松地自己编写...)

所以,足够的咆哮;最重要的是,有没有办法控制我的单元测试运行的顺序?

[TestMethod]
[Priority(0)]

等。似乎不起作用,这是有道理的,因为微软说它不会。

另外,请不要评论“违反隔离”。 TestClass隔离了我正在测试的内容,而不是单独的TestMethods。无论如何,每个测试都可以独立运行,它们不能以随机顺序一起运行,因为没有办法拆除静态类。

哦,我也知道“有序测试”。

11 个答案:

答案 0 :(得分:105)

您可以使用播放列表

右键点击测试方法 - >添加到播放列表 - >新播放列表

执行顺序将在您将其添加到播放列表时,但如果您想要更改它,则您拥有该文件

enter image description here

答案 1 :(得分:47)

将您的测试合并到一个巨大的测试中将起作用。为了使测试方法更具可读性,您可以执行类似

的操作
[TestMethod]
public void MyIntegratonTestLikeUnitTest()
{
    AssertScenarioA();

    AssertScenarioB();

    ....
}

private void AssertScenarioA()
{
     // Assert
}

private void AssertScenarioB()
{
     // Assert
}

实际上,您提出的问题可能是您应该提高实施的可测试性。

答案 2 :(得分:9)

我没有看到有人提到ClassInitialize属性方法。属性非常简单。

创建标有[ClassInitialize()][TestInitialize()]属性的方法,以准备运行单元测试的环境的各个方面。这样做的目的是建立一个运行单元测试的已知状态。例如,您可以使用[ClassInitialize()][TestInitialize()]方法复制,更改或创建测试将使用的某些数据文件。

创建标有[ClassCleanup()][TestCleanUp{}]属性的方法,以在运行测试后将环境返回到已知状态。这可能意味着删除文件夹中的文件或将数据库返回到已知状态。例如,在测试订单输入应用程序中使用的方法后,将库存数据库重置为初始状态。

  • [ClassInitialize()]请先使用ClassInitialize运行代码 在课堂上进行第一次测试。

  • [ClassCleanUp()]使用ClassCleanup在所有测试后运行代码 一个班已经开始了。

  • [TestInitialize()]在运行之前使用TestInitialize运行代码 每次测试。

  • [TestCleanUp()]使用TestCleanup在每次测试后运行代码 运行

答案 3 :(得分:6)

正如评论者已经指出的那样,依赖于其他测试的测试指出了设计缺陷。然而,有办法实现这一目标。如前面提到的问题 here 所述,您可以创建有序单元测试,这基本上是一个确保测试顺序的单个测试容器。

以下是MSDN指南:http://msdn.microsoft.com/en-us/library/ms182631.aspx

答案 4 :(得分:4)

由于您已经提到了Visual Studio测试框架提供的Ordered Test功能,我将忽略它。你似乎也意识到你为了测试这个静态类而想要完成的是一个“坏主意”,所以我会忽略它。

相反,让我们专注于您实际上能够保证您的测试按您想要的顺序执行的方式。一个选项(由@gaog提供)是“一个测试方法,许多测试函数”,在标记有TestMethod属性的单个函数中按照您想要的顺序调用测试函数。这是最简单的方法,唯一的缺点是第一个失败的测试函数将阻止任何剩余的测试函数执行

根据您对情况的描述,我建议您使用这个解决方案。

如果粗体部分是您的问题,您可以通过利用内置的数据驱动测试功能来完成隔离测试的有序执行。它更复杂,感觉有点脏,但它完成了工作。

简而言之,您定义了一个数据源(如CSV文件或数据库表),它控制运行测试的顺序,以及实际包含测试功能的函数的名称。然后,您可以将该数据源挂接到数据驱动的测试中,使用顺序读取选项,并按照您想要的顺序执行您的功能,作为单独的测试。

[TestClass]
public class OrderedTests
{
    public TestContext TestContext { get; set; }

    private const string _OrderedTestFilename = "TestList.csv";

    [TestMethod]
    [DeploymentItem(_OrderedTestFilename)]
    [DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", _OrderedTestFilename, _OrderedTestFilename, DataAccessMethod.Sequential)]
    public void OrderedTests()
    {
        var methodName = (string)TestContext.DataRow[0];
        var method = GetType().GetMethod(methodName);
        method.Invoke(this, new object[] { });
    }

    public void Method_01()
    {
        Assert.IsTrue(true);
    }

    public void Method_02()
    {
        Assert.IsTrue(false);
    }

    public void Method_03()
    {
        Assert.IsTrue(true);
    }
}

在我的示例中,我有一个名为TestList.csv的支持文件,该文件被复制到输出。它看起来像这样:

TestName
Method_01
Method_02
Method_03

您的测试将按照您指定的顺序执行,并且在正常的测试隔离中(即如果一个失败,其余的仍然执行,但共享静态类)。

以上只是基本的想法,如果我在生产中使用它,我会在测试运行之前动态生成测试函数名称及其顺序。也许通过利用您发现的PriorityAttribute和一些简单的反射代码来提取类中的测试方法并对它们进行适当的排序,然后将该顺序写入数据源。

答案 5 :(得分:3)

这是一个类,可用于独立于MS Ordered Tests框架设置和运行有序测试,无论出于何种原因 - 比如不必在构建机器上调整mstest.exe参数,或者在非有序的情况下混合有序一类。

原始测试框架只将有序测试列表视为单个测试,因此任何init / cleanup如[TestInitalize()] Init()仅在整个集合之前和之后调用。

<强>用法:

        [TestMethod] // place only on the list--not the individuals
        public void OrderedStepsTest()
        {
            OrderedTest.Run(TestContext, new List<OrderedTest>
            {
                new OrderedTest ( T10_Reset_Database, false ),
                new OrderedTest ( T20_LoginUser1, false ),
                new OrderedTest ( T30_DoLoginUser1Task1, true ), // continue on failure
                new OrderedTest ( T40_DoLoginUser1Task2, true ), // continue on failure
                // ...
            });                
        }

<强>实施

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace UnitTests.Utility
{    
    /// <summary>
    /// Define and Run a list of ordered tests. 
    /// 2016/08/25: Posted to SO by crokusek 
    /// </summary>    
    public class OrderedTest
    {
        /// <summary>Test Method to run</summary>
        public Action TestMethod { get; private set; }

        /// <summary>Flag indicating whether testing should continue with the next test if the current one fails</summary>
        public bool ContinueOnFailure { get; private set; }

        /// <summary>Any Exception thrown by the test</summary>
        public Exception ExceptionResult;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="testMethod"></param>
        /// <param name="continueOnFailure">True to continue with the next test if this test fails</param>
        public OrderedTest(Action testMethod, bool continueOnFailure = false)
        {
            TestMethod = testMethod;
            ContinueOnFailure = continueOnFailure;
        }

        /// <summary>
        /// Run the test saving any exception within ExceptionResult
        /// Throw to the caller only if ContinueOnFailure == false
        /// </summary>
        /// <param name="testContextOpt"></param>
        public void Run()
        {
            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                ExceptionResult = ex;
                throw;
            }
        }

        /// <summary>
        /// Run a list of OrderedTest's
        /// </summary>
        static public void Run(TestContext testContext, List<OrderedTest> tests)
        {
            Stopwatch overallStopWatch = new Stopwatch();
            overallStopWatch.Start();

            List<Exception> exceptions = new List<Exception>();

            int testsAttempted = 0;
            for (int i = 0; i < tests.Count; i++)
            {
                OrderedTest test = tests[i];

                Stopwatch stopWatch = new Stopwatch();
                stopWatch.Start();

                testContext.WriteLine("Starting ordered test step ({0} of {1}) '{2}' at {3}...\n",
                    i + 1,
                    tests.Count,
                    test.TestMethod.Method,
                    DateTime.Now.ToString("G"));

                try
                {
                    testsAttempted++;
                    test.Run();
                }
                catch
                {
                    if (!test.ContinueOnFailure)
                        break;
                }
                finally
                {
                    Exception testEx = test.ExceptionResult;

                    if (testEx != null)  // capture any "continue on fail" exception
                        exceptions.Add(testEx);

                    testContext.WriteLine("\n{0} ordered test step {1} of {2} '{3}' in {4} at {5}{6}\n",
                        testEx != null ? "Error:  Failed" : "Successfully completed",
                        i + 1,
                        tests.Count,
                        test.TestMethod.Method,
                        stopWatch.ElapsedMilliseconds > 1000
                            ? (stopWatch.ElapsedMilliseconds * .001) + "s"
                            : stopWatch.ElapsedMilliseconds + "ms",
                        DateTime.Now.ToString("G"),
                        testEx != null
                            ? "\nException:  " + testEx.Message +
                                "\nStackTrace:  " + testEx.StackTrace +
                                "\nContinueOnFailure:  " + test.ContinueOnFailure
                            : "");
                }
            }

            testContext.WriteLine("Completed running {0} of {1} ordered tests with a total of {2} error(s) at {3} in {4}",
                testsAttempted,
                tests.Count,
                exceptions.Count,
                DateTime.Now.ToString("G"),
                overallStopWatch.ElapsedMilliseconds > 1000
                    ? (overallStopWatch.ElapsedMilliseconds * .001) + "s"
                    : overallStopWatch.ElapsedMilliseconds + "ms");

            if (exceptions.Any())
            {
                // Test Explorer prints better msgs with this hierarchy rather than using 1 AggregateException().
                throw new Exception(String.Join("; ", exceptions.Select(e => e.Message), new AggregateException(exceptions)));
            }
        }
    }
}

答案 6 :(得分:3)

正如你现在应该知道的那样,纯粹主义者说它是禁止进行有序测试的。单元测试可能也是如此。 MSTest和其他单元测试框架用于运行纯单元测试,但也用于UI测试,完整集成测试,您可以命名。也许我们不应该将它们称为单元测试框架,或者我们应该根据我们的需要使用它们。这就是大多数人所做的事情。

我正在运行VS2015并且我必须按给定的顺序运行测试,因为我正在运行UI测试(Selenium)。

优先级 - 根本不做任何事情 This attribute is not used by the test system. It is provided to the user for custom purposes.

orderedtest - 它有效,但我不推荐它,因为:

  1. orderedtest 一个文本文件,按照应该的顺序列出您的测试 执行。如果更改方法名称,则必须修复该文件。
  2. 测试执行顺序在类中受到尊重。你不能订购 哪个类首先执行测试。
  3. orderedtest 文件绑定到配置(Debug或Release
  4. 您可以拥有多个 orderedtest 文件,但不能在不同的 orderedtest 文件中重复给定的方法。所以你不能有一个 orderedtest 文件用于Debug而另一个用于Release。
  5. 此主题中的其他建议很有趣,但您无法在测试资源管理器上跟踪测试进度。

    您将获得纯粹主义者建议的解决方案,但实际上是有效的解决方案:按声明顺序排序

    MSTest执行程序使用一个管理获取声明顺序的互操作,这个技巧将一直有效,直到Microsoft更改测试执行程序代码。

    这意味着首先声明的测试方法在第二位声明的测试方法之前执行,等等。

    为了让您的生活更轻松,声明顺序应与测试资源管理器中显示的字母顺序相匹配。

    • A010_FirstTest
    • A020_SecondTest
    • A100_TenthTest

    我强烈建议一些陈旧且经过测试的规则:

    • 使用10步,因为稍后需要在
    • 上插入测试方法
    • 通过在测试编号之间使用大量步骤来避免重新编号测试
    • 如果您运行的测试超过10次,则使用3位数对您的测试进行编号
    • 如果您运行的测试超过100次,则使用4位数对您的测试进行编号

    非常重要

    要通过声明顺序执行测试,您必须在测试资源管理器中使用全部运行

    假设您有3个测试类(在我的测试中针对Chrome,Firefox和Edge)。如果您选择一个给定的类并右键单击运行选定的测试,通常会先执行最后一个地方声明的方法。

    同样,正如我之前所说,宣布订单列出的订单应该匹配,否则您很快就会遇到大麻烦。

答案 7 :(得分:2)

对不起,我不会解决测试的顺序。其他人已经做到了。此外,如果你知道“有序测试” - 嗯,这是MS VS对这个问题的回应。我知道那些有序测试并不好玩。但是他们认为这将是“它”,而MSTest中确实没有更多的内容。

我写了你的一个假设:

  

因为没有办法拆除静态课程。

除非您的静态类代表代码外部的某个进程范围外部状态(例如,代码的其余部分调用P /调用的非托管本机DLL库的状态),假设there is no way不正确。

如果你的静态类引用了这个,那么对不起,你是完全正确的,这个问题的其余部分是无关紧要的。不过,正如你没有说的那样,我认为你的代码是“托管”的。

考虑并查看AppDomain内容。很少需要它,但是当你可能喜欢使用它时就是这种情况。

您可以创建一个新的AppDomain,并在那里实例化测试,并在那里运行测试方法。托管代码使用的静态数据将在那里隔离,完成后,您将能够卸载AppDomain,并且所有数据(包括静态)将会消失。然后,下一个测试将初始化另一个appdomain,依此类推。

除非您有必须跟踪的外部状态,否则这将有效。 AppDomains仅隔离托管内存。任何本机DLL仍然是每个进程加载,它们的状态将由所有AppDomain共享。

此外,创建/拆除应用程序域将会减慢测试速度。此外,您可能在子appdomain中遇到装配解析问题,但它们可通过合理数量的可重用代码解决。

此外,将测试数据传递给子AppDomain可能会遇到小问题。传递的对象要么必须以某种方式可序列化,要么MarshalByRef等等。说跨域就像IPC一样。

然而,在这里注意,它将100%管理谈话。如果您需要额外注意并为AppDomain设置添加一些工作,您甚至可以传递委托并在目标域中运行它们。然后,您可以将测试包装为:

,而不是进行一些毛茸茸的跨域设置
void testmethod()
{
    TestAppDomainHelper.Run( () =>
    {
        // your test code
    });
}

甚至

[IsolatedAppDomain]
void testmethod()
{
    // your test code
}

如果您的测试框架支持创建此类包装器/扩展。经过一些初步的研究和工作,使用它们几乎是微不足道的。

答案 8 :(得分:0)

我看到这个主题已有6年历史了,现在我们有了Visual Studio的新版本,但是无论如何我都会答复。 我在Visual Studio 19中遇到了顺序问题,我通过在方法名称之前以如下字母顺序添加大写字母(也可以添加小写字母)来解决这个问题:

[TestMethod]
        public void AName1()
        {}
[TestMethod]
        public void BName2()
        {}

以此类推。 我知道这看起来并不吸引人,但看起来Visual似乎是按字母顺序在测试资源管理器中对测试进行排序,与如何在代码中编写代码无关。 在这种情况下,播放列表对我不起作用。

希望这会有所帮助。

答案 9 :(得分:0)

如果您可以使用 NUnit framwork,则可以使用 [Order] 属性。

有关使用 NUnit 的测试排序,请参阅 MS doc

using NUnit.Framework;

namespace NUnit.Project
{
    public class ByOrder
    {
        public static bool Test1Called;
        public static bool Test2ACalled;
        public static bool Test2BCalled;
        public static bool Test3Called;

        [Test, Order(5)]
        public void Test1()
        {
            Test3Called = true;

            Assert.IsTrue(Test1Called);
            Assert.IsFalse(Test2ACalled);
            Assert.IsTrue(Test2BCalled);
        }

        [Test, Order(0)]
        public void Test2B()
        {
            Test2BCalled = true;

            Assert.IsTrue(Test1Called);
            Assert.IsFalse(Test2ACalled);
            Assert.IsFalse(Test3Called);
        }

        [Test]
        public void Test2A()
        {
            Test2ACalled = true;

            Assert.IsTrue(Test1Called);
            Assert.IsTrue(Test2BCalled);
            Assert.IsTrue(Test3Called);
        }

        [Test, Order(-5)]
        public void Test3()
        {
            Test1Called = true;

            Assert.IsFalse(Test2ACalled);
            Assert.IsFalse(Test2BCalled);
            Assert.IsFalse(Test3Called);
        }
    }
} 

答案 10 :(得分:-2)

  

他们无法以随机顺序一起运行,因为无法拆除静态类

您可以按字母顺序命名命名空间和类。例如:

  • MyApp.Test。的 Stage01 _setup。的 Step01 _BuildDB
  • MyApp.Test。的 Stage01 _setup。的步骤02 _UpgradeDB
  • MyApp.Test。的 Stage02 _domain。的 Step01 _TestMyStaff
  • MyApp.Test。的 Stage03 _Integration。的 Step01 _TestMyStaff

其中MyApp.Test.Stage01_Setup是命名空间,Step01_BuildDB是类名。