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


78

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

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

因此,我发现此http://msdn.microsoft.com/zh-cn/library/microsoft.visualstudio.testtools.unittesting.priorityattribute.aspx在“备注”部分中说:“测试系统未使用此属性。它是为自定义目的提供给用户的。” ??那有什么好处呢?他们是否希望我编写自己的测试包装程序以利用这个出色的属性(如果我想付出那么多的努力,就可以轻松编写自己的内容……)

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

[TestMethod]
[Priority(0)]

等等似乎不起作用,这是有道理的,因为Microsoft表示不会。

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

哦,我也知道“订购测试”。


3
您能否解释为什么测试依赖于订单?我认为测试实质上是对静态类进行增量测试?
Todd Bowles 2013年

13
您的单元测试不应取决于顺序。这个脑残的静态类使您的代码不可测试。如果您不能“拆除”,那么这不是单元测试时唯一要解决的问题。
约翰·桑德斯

4
静态类不是我的-是的,它应该被写为单例。不幸的是,有时候您只需要打出发给您的(糟糕的)纸牌即可。我正在尽可能地使用Fakes将其从等式中删除,但我无法消除它。
iGanja 2013年

3
您不能每次在TestInitialize中重置静态类上下文吗?单元测试的基本原则之一是独立性,不要试图控制执行顺序。您不是在“违反隔离”,而是在违反使测试成为单元测试的基本原则。
皮埃尔·卢克·皮诺

6
有很多原因可以运行有序测试。当需要运行有序测试时,一个人真的不需要真正无济于事的注释,例如说您不应该这样做,等等。我在礼貌地询问下一次,请跳过这种注释,尝试有所帮助。或者只是完全跳过线程。一分钟后,我将添加答案。
Tiago Freitas Leal

Answers:


55

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

[TestMethod]
public void MyIntegratonTestLikeUnitTest()
{
    AssertScenarioA();

    AssertScenarioB();

    ....
}

private void AssertScenarioA()
{
     // Assert
}

private void AssertScenarioB()
{
     // Assert
}

实际上,您所提出的问题建议您可能应该提高实现的可测试性。


2
合并测试是一种公平的方法,但是如果列表中的第一个测试方法未通过断言,则其他任何方法都不会执行。考虑到OP测试策略固有的顺序依赖性,这可能不是问题。
Todd Bowles 2013年

1
同意@ToddBowles,这可能是解决方法。当然,正如您所说的,如果使用大量的Asserts进行大型测试,则在失败时会失去一些粒度。+1
iGanja

这可能不是最好的解决方案(重构静态类是),但这当然是最容易实现的,并且让我再次从事其他工作。
iGanja 2013年

请参阅下面有关ClassInitialize属性的评论,我也相信OrderedTests相当容易实现,并且是MS接受的方式。
MattyMerrix

3
这可能有效,但无法达到单元测试的目的。单元测试的想法是将零件分解成小块以便进行快速测试-而不是将它们粉碎在一起,
Christian Findlay

115

您可以使用播放列表

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

执行顺序将是您将其添加到播放列表时的顺序,但是如果您要更改播放顺序,则有文件

在此处输入图片说明


20
刚刚在VS2015中尝试过,看来播放列表不会影响执行顺序。而是按照声明方法的顺序运行它们。
2015年

28
@Jrd在Visual Studio 2015中,情况有所变化。在解决方案资源管理器中,右键单击单元测试项目,然后单击“添加”>“ OrderedTest”。这样做会向项目添加一个新文件。打开此文件时,您可以单击项目中的测试方法,并将它们添加1次或多次到此测试中。
Zee

请参阅下面有关ClassInitialize属性的评论,我也相信OrderedTests相当容易实现,并且是MS接受的方式。
MattyMerrix

可以通过命令行调用播放列表,以便在构建服务器上使用吗?
red888

1
@EA您应该考虑作者回答的日期和时间。有趣的是->他回答了2013,您评论了2015,我评论了2017。很棒; P :);)
RajeshKdev

15

如您现在所知,纯粹主义者说禁止运行有序测试。对于单元测试可能是正确的。MSTest和其他单元测试框架不仅用于运行纯单元测试,而且还用于UI测试,完全集成测试。也许我们不应该将它们称为单元测试框架,或者应该根据需要使用它们。无论如何,这就是大多数人所做的事情。

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

优先级-根本不执行任何操作 测试系统未使用此属性。提供给用户用于自定义目的。

orderedtest-它可以工作,但我不建议这样做,因为:

  1. 一个orderedtest一个文本文件,列出了它们应该被执行的顺序你的测试。如果更改方法名称,则必须修复该文件。
  2. 在类内部遵守测试执行顺序。您无法命令哪个类首先执行其测试。
  3. 一个orderedtest文件绑定到配置,Debug或推出
  4. 您可以有几个orderedtest文件,但是给定的方法不能在其他orderedtest文件中重复。因此,对于Debug,您将没有一个orderedtest文件;对于Release,您将没有另一个。

该线程中的其他建议很有趣,但是您失去了在Test Explorer上关注测试进度的能力。

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

MSTest执行程序使用一个互操作程序来设法获取声明顺序,并且该技巧将起作用,直到Microsoft更改测试执行程序代码为止。

这意味着首先声明的测试方法要在第二个声明的方法之前执行,依此类推。

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

  • A010_FirstTest
  • A020_SecondTest
  • 等等
  • A100_TenthTest

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

  • 使用10步,因为稍后您将需要插入测试方法
  • 避免在测试编号之间使用足够大的步骤来重新编号测试编号
  • 如果您运行的测试超过10个,请使用3位数字为您的测试编号
  • 如果您运行的测试超过100个,请使用4位数字为测试编号

很重要

为了按照声明顺序执行测试,必须在“测试资源管理器”中使用“全部运行”

假设您有3个测试类(在我的案例中是针对Chrome,Firefox和Edge的测试)。如果选择给定的类并右键单击“运行选定的测试”,则通常从执行最后声明的方法开始。

再次,如我之前所说,声明的顺序列出的顺序应该匹配,否则您很快就会遇到大麻烦。


做一组功能写到数据库的测试。不在乎它们是否真的按顺序运行。但是如果碰巧,最后一个是最完整的测试。如果提示最后运行,那就很好。是。我也在嘲笑。单元测试与功能测试分开。在我的主要部署过程中,仅针对本地/测试环境手动运行FT。我不会针对功能上的微小更改和修补程序运行功能测试。这很好用!
TamusJRoyce

我正在使用VS2019,并且测试按字母顺序运行,没有我的任何干预。那正是我所需要的。我对您的答案投了赞成票,因为您建议将测试命名为几十个,因此以后可以在不重命名已经完成的所有内容的情况下插入一些内容。
MsTapp '19

12

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

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

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

  • [ClassInitialize()]使用ClassInitialize你运行的类的第一个测试之前运行代码。

  • [ClassCleanUp()]使用ClassCleanup类中的所有测试都运行之后运行的代码。

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

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


3
还有AssemblyInitialize和AssemblyCleanup,分别在每个测试运行的开始和结束时运行。
MattyMerrix

6

既然您已经提到了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和一些简单的反射代码来提取类中的测试方法并对其进行适当排序,然后将该顺序写入数据源。


4

这是一个可以出于任何原因独立于MS Ordered Tests框架设置和运行有序测试的类,例如不必在生成机上调整mstest.exe参数,或在类中混合有序与无序。

原始测试框架仅将已排序测试的列表视为单个测试,因此,任何初始化/清除(如[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)));
            }
        }
    }
}

2

抱歉,我不会介绍测试顺序。其他人已经做到了。另外,如果您了解“有序测试”,那么这就是MS VS对问题的回应。我知道那些有序的测试很不好玩。但是他们认为这将是“它”,而实际上在MSTest中没有更多的东西了。

我写关于您的一个假设:

因为没有办法拆除静态类。

除非您的静态类表示代码外部的某些进程范围的外部状态(例如,由其余代码调用的非托管本机DLL库的状态),there is no way否则您的假设将不成立。

如果您的静态类对此进行了说明,那么对不起,您说得很对,其余的问题无关紧要。不过,正如您未说的那样,我假设您的代码是“托管的”。

思考并检查 AppDomain事物。很少需要它,但是当您可能想使用它们时,正是这种情况。

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

除非您具有必须跟踪的外部状态,否则这将起作用。AppDomains仅隔离托管内存。任何本机DLL仍将按进程加载,并且它们的状态将由所有AppDomain共享。

同样,创建/删除应用程序域也会降低测试速度。此外,您可能在子应用程序域中的程序集解析方面遇到问题,但可以通过合理数量的可重用代码来解决这些问题。

另外,在将测试数据传递到子AppDomain以及从子AppDomain传回测试数据时,您可能会遇到小问题。传递的对象要么必须以某种方式可序列化,要么必须是MarshalByRefor。否则,跨域对话几乎就像IPC。

但是,请注意,这将是100%托管的交谈。如果您格外注意并在AppDomain设置中添加一些工作,您甚至可以传递委托并在目标域中运行它们。然后,您无需进行繁琐的跨域设置,而是可以将测试包装到以下内容中:

void testmethod()
{
    TestAppDomainHelper.Run( () =>
    {
        // your test code
    });
}

甚至

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

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


我会研究这个。也许不是今天。:)
iGanja

0

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

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

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

希望这会有所帮助。


如果能奏效,那就太好了。我将不得不尝试。谢谢!
iGanja

1
这似乎在VS 2019中有效,但我不喜欢它。谁想要这样重命名所有测试方法?这是丑陋且不直观的。他们确实需要提供一种设置测试执行顺序的方法。
Mike Lowery

-2

它们只是不能以随机顺序一起运行,因为无法拆除静态类

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

  • MyApp.Test。Stage01 _setup。Step01 _BuildDB
  • MyApp.Test。Stage01 _setup。Step02 _UpgradeDB
  • MyApp.Test。Stage02 _Domain。Step01 _TestMyStaff
  • MyApp.Test。Stage03 _Integration。Step01 _TestMyStaff

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


执行顺序是按方法声明顺序而不是字母顺序。
Tiago Freitas Leal

1
@TiagoFreitasLeal在VS 2019中不正确!Microsoft不断改变其工作方式。
Mike Lowery
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.