控制Visual Studio中unit testing的执行顺序

好的,我已经完成了这方面的好消息。 我有一系列的unit testing,调用一个静态类,一旦初始化,设置属性不能(或我不希望)改变。

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

所以,我发现这个http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.priorityattribute.aspx在备注部分说:“该属性不被testing系统使用。提供给用户用于定制目的。“ 咦? 那有什么好处呢? 他们是否期望我写自己的testing封装来利用这个神话般的属性(如果我想要达到这个水平的努力,我可以轻松写出自己的属性…)

所以,足够的咆哮; 底线,有没有一种方法来控制我的unit testing运行的顺序?

[TestMethod] [Priority(0)] 

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

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

哦,我也知道“有序testing”。

把你的testing合并成一个巨大的testing将会起作用。 为了使testing方法更具可读性,你可以做类似的事情

 [TestMethod] public void MyIntegratonTestLikeUnitTest() { AssertScenarioA(); AssertScenarioB(); .... } private void AssertScenarioA() { // Assert } private void AssertScenarioB() { // Assert } 

实际上,你所提出的问题表明你可能应该提高实现的可testing性。

您可以使用播放列表

右键点击testing方法 – >添加到播放列表 – >新build播放列表

执行顺序将被添加到播放列表中,但是如果您想要更改它,则会有该文件

在这里输入图像说明

正如评论者已经指出的那样,依赖于其他testing的testing指出了一个devise缺陷。 尽pipe如此,还是有办法做到的。 正如以前在这里提出的问题所回答的,您可以创build有序的unit testing,这基本上是确保testing顺序的单个testing容器。

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

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

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

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

[ClassInitialize()]在运行类中的第一个testing之前,使用ClassInitialize来运行代码。

[ClassCleanUp()]在类中的所有testing运行之后,使用类清理来运行代码。

[TestInitialize()]使用TestInitialize在运行每个testing之前运行代码。

[TestCleanUp()]使用TestCleanup在每个testing运行后运行代码。

由于您已经提到了Visual Studiotesting框架提供的有序testingfunction,所以我会忽略这一点。 你似乎也意识到,为了testing这个静态类你试图完成的是一个“坏主意”,所以我会忽略这个。

相反,让我们专注于如何确保您的testing能够按照您所需的顺序执行。 一个选项(由@gaog提供)是“一种testing方法,许多testingfunction”,按照你所希望的从一个标有TestMethod属性的函数中调用你的testing函数。 这是最简单的方法,唯一的缺点是第一个失败的testing函数会阻止任何剩余的testing函数执行

随着你的情况描述,这是我build议你使用的解决scheme。

如果粗体部分对您来说是一个问题,您可以利用内置的数据驱动testingfunction来完成隔离testing的有序执行。 它更复杂,感觉有点肮脏,但它完成了工作。

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

 [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 

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

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

我不会解决testing的顺序,对不起。 其他人已经做到了。 另外,如果你知道“有序testing” – 那么这是MS VS对这个问题的回应。 我知道那些有序的testing是没有趣味的。 但他们认为这将是“它”,并没有什么更多的MSTest关于这一点。

我写下你的一个假设:

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

除非你的静态类代表了你的代码外部的一些进程范围的外部状态 (比如你的代码的其余部分调用了一个非托pipe的本地DLL库的状态), there is no way你认为there is no way假设是不正确的。

如果你的静态类指的是这个,那么对不起,你是完全正确的,这个沉没的其余部分是不相关的。 不过,正如你不这样说,我假设你的代码是“pipe理”的。

想想,并检查AppDomain东西。 很less需要它,但是当你可能想要使用它们时,情况正是如此。

您可以创build一个新的AppDomain,并在那里实例化testing,然后在那里运行testing方法。 托pipe代码使用的静态数据将在那里隔离,完成后,您将能够卸载AppDomain,并且所有包含静态数据的数据都将被蒸发。 然后,下一个testing将初始化另一个AppDomain,等等。

这将工作,除非你有外部状态,你必须跟踪。 AppDomain只隔离托pipe内存。 任何本地DLL仍然是加载每个进程,他们的状态将被所有AppDomain共享。

另外,创build/拆除应用程序域也会减慢testing速度。 另外,你可能在子appdomain中的程序集parsing有问题,但是它们可以用合理数量的可重用代码解决。

另外,将testing数据传递给AppDomain的子AppDomain可能会有小问题。 传递的对象将不得不以某种方式被序列化,或者被MarshalByRef或者等等。谈论跨域就像IPC一样。

但是,在这里照顾,这将是100%pipe理谈话。 如果您需要特别注意并在AppDomain安装中添加一些工作,您甚至可以通过代理并在目标域中运行它们。 然后,而不是做一些毛茸茸的跨域设置,你可以包装你的testing类似于:

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

甚至

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

如果你的testing框架支持创build这样的包装器/扩展。 经过一些初步的研究和工作,使用它们几乎是微不足道的。

这里有一个类可以用来设置和运行独立于MS Ordered Tests框架的有序testing,无论出于何种原因 – 比如不需要在构build机器上调整mstest.exe参数,或者在一个类中调用无序的命令。

原来的testing框架只将有序testing的列表视为单个testing,所以像[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))); } } } } 

他们只是不能随机排列在一起,因为没有办法拆除静态类

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

  • MyApp.Test。 Stage01 _Setup。 Step01 _BuildDB
  • MyApp.Test。 Stage01 _Setup。 Step02 _UpgradeDB
  • MyApp.Test。 Stage02_Domain。 Step01 _TestMyStaff
  • MyApp.Test。 Stage03集成。 Step01 _TestMyStaff

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

正如你现在应该知道的那样,纯粹主义者说它是禁止运行有序testing的。 unit testing可能是这样的。 MSTest和其他unit testing框架用于运行纯粹的unit testing,还包括UItesting,完整的集成testing,您可以将其命名。 也许我们不应该称之为unit testing框架,也许我们应该根据自己的需要来使用它们。 无论如何,这是大多数人所做的。

我正在运行VS2015,我必须按给定的顺序运行testing,因为我正在运行UItesting(Selenium)。

优先级 – 根本不执行任何操作此属性不被testing系统使用。 它提供给用户用于定制目的。

orderedtest – 它的作品,但我不推荐它,因为:

  1. 一个有序testing的文本文件,按照它们应该执行的顺序列出你的testing。 如果您更改方法名称,则必须修复该文件。
  2. testing执行顺序在课堂上受到尊重。 你不能命令哪个类首先执行它的testing。
  3. 一个有序testing文件绑定到一个configuration,debugging或发布
  4. 你可以有几个orderedtest文件,但是一个给定的方法不能在不同的orderedtest文件中重复。 所以你不能有一个有序的testing文件的debugging和另一个版本。

在这个线程中的其他build议很有趣,但是你没有能力按照testing浏览器上的testing进度。

你只剩下纯粹主义者会build议的解决scheme,但实际上是解决scheme: 按声明顺序sorting

MSTest执行程序使用一个互操作来pipe理得到声明的顺序,这个技巧将工作,直到微软更改testing执行程序代码。

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

为了让您的生活更轻松,声明顺序应该与testing浏览器中显示的字母顺序相匹配。

  • A010_FirstTest
  • A020_SecondTest
  • 等等
  • A100_TenthTest

我强烈build议一些旧的和testing的规则:

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

很重要

为了通过声明顺序执行testing,您必须使用testing资源pipe理器中的“全部运行”

假设你有3个testing类(在我的情况下testingChrome,Firefox和Edge)。 如果你select一个给定的类,并右键单击运行选定的testing,它通常开始执行在最后一个地方声明的方法。

同样,正如我之前所说的那样, 宣布的秩序上市的秩序应该匹配,否则你很快就会陷入困境。