【问题标题】:NUnit Test Run OrderNUnit 测试运行顺序
【发布时间】:2009-07-03 09:51:26
【问题描述】:

默认情况下,nunit 测试按字母顺序运行。有谁知道设置执行顺序的任何方法?是否存在此属性?

【问题讨论】:

  • 您为什么要这样做?听起来您依赖于运行顺序,这是一件坏事。您需要重新考虑为什么要这样做。单元测试应该独立运行并且完全独立于其他测试。听起来您正在为测试气味创建候选者Erratic Tests
  • 这看起来像是重复的,但你可以看到我对此的回答 here
  • @RichardOD - 应该!= 必须。这真的不是一个应该发生的事情,因为实际上几乎所有的集成测试都是按顺序运行的——问问你的 QA 团队是否随机安排了他们的测试顺序。
  • 我目前有一些测试的顺序似乎很重要,尽管它不应该。就个人而言,我想要一种专门随机化测试顺序的方法,以帮助我确保我的测试不会以某种方式依赖于顺序。当然,我真正想要的是一个测试运行器,它会以随机顺序运行我的所有测试,直到它发现问题,或者我说停止。如果我跑了一夜,早上一切仍然是绿色的,那么我可以确信我已经消除了最后一个意外的副作用。
  • 如果问题被更新以指定我们在这里讨论集成测试,这个问题将变得更加相关......我们都知道有关单元测试的规则,至少如果您阅读过 XUnit 测试模式并遵循鲍勃叔叔等.. 你这样做.. 但是 NUnit 等框架对于快速启动和运行集成测试也非常有用.. 你绝对不希望这些是随机的,特别是当一个昂贵的数据库时涉及设置..

标签: c# .net attributes nunit tdd


【解决方案1】:

我只想指出,虽然大多数响应者认为这些是单元测试,但问题并没有具体说明它们是。

nUnit 是一款出色的工具,可用于各种测试情况。我可以看到想要控制测试顺序的适当原因。

在这些情况下,我不得不求助于将运行顺序合并到测试名称中。能够使用属性指定运行顺序会很棒。

【讨论】:

  • 你的意思是你调用了你的测试,例如001_first_test002_second_test等等?
  • 是的,没错,虽然我通常使用的模式可以让我在必要时轻松插入测试,所以可能是 010_first_test、020_second_test 等。
  • 感谢您在这里唯一理智的回答。这是一个具体的问题,但不知何故,模糊的迂腐答案正在被赞成。是的,我们都知道单元测试应该是什么,但这不是问题。
  • 您应该依赖字母顺序的运行顺序。许多测试运行程序在许多线程或进程上同时运行您的测试,不一定按字母顺序。例如,NCrunch 根据您更改的代码(受影响的测试)对测试进行优先级排序,然后是上次是否失败,然后是运行速度快还是慢。如果您想要定义的顺序,只需为这些测试创建一个元运行器并将它们从常规运行中排除。
  • 指定顺序的另一个很好的理由是在继续测试套件的其余部分之前先捕获某些问题。换句话说,如果我创建用户的测试失败,那么在我发现这个结果之前,我不需要运行其他所有东西。在我的另一个测试中,我可能会嘲笑用户,但对我来说重要的是这是第一个失败的测试 - 特别是当测试套件很大时。
【解决方案2】:

NUnit 3.2.0 添加了一个OrderAttribute,参见:

https://github.com/nunit/docs/wiki/Order-Attribute

例子:

public class MyFixture
{
    [Test, Order(1)]
    public void TestA() { ... }


    [Test, Order(2)]
    public void TestB() { ... }

    [Test]
    public void TestC() { ... }
}

【讨论】:

  • 谢谢,直截了当地寻找我想要的东西 - 没有讨论,为什么它是好还是坏:)
  • 然后他们把它变成一个整数,而不是十进制,所以如果必须插入测试,所有测试都必须重新编号。
  • 如果你想以这种方式引入中间项目,你总是可以从非常大的数字开始,比如数百万或其他东西——只是更多的输入。 Imo 应该有一种比使用数字作为优先级(如方法依赖树)更简单的方法来排序,但每个都有它自己的缺点/优点。
  • 好答案,但请注意,文档状态测试不会等待之前的测试完成。
  • 有趣的是,根据我的经验,如果我们分别添加 Test 和 Order 属性,则无法保证测试的顺序。
【解决方案3】:

您的每个单元测试都应该能够独立运行和独立运行。如果它们满足此标准,则顺序无关紧要。

但在某些情况下,您需要先运行某些测试。一个典型的例子是在持续集成的情况下,一些测试比其他的运行时间更长。我们使用类别属性,以便我们可以在使用数据库的测试之前运行使用模拟的测试。

即把它放在你快速测试的开始

[Category("QuickTests")]

如果您有依赖于特定环境条件的测试,请考虑 TestFixtureSetUpTestFixtureTearDown 属性,它们允许您标记要在测试之前和之后执行的方法.

【讨论】:

  • @Chris,我倾向于不使用这些属性,这是一篇有趣的博文-jamesnewkirk.typepad.com/posts/2007/09/why-you-should-.html。不过,关于类别测试的观点很好。
  • 另一个顺序依赖测试的例子是当你想使用你的 nunit 框架来运行集成测试时......
  • @ByronRoss:当我这样做时,我倾向于使测试更大,并尽可能地依靠现有的单元测试,这样我就可以编写更少的测试。然后每个完整的运行都可以单独失败。我还尝试设计我的数据,使其可以与现有数据分开存在,而不是依赖现有数据。
  • 如果您不按随机顺序运行测试,那么如何验证它们在乱序运行时是否确实有效?您所说的类似于说“如果您的软件没有错误,则无需进行测试”。
  • @jforberg:如果你有一个随机发生的故障,你说什么时候修复了?
【解决方案4】:

希望测试以特定顺序运行并不意味着测试相互依赖 - 我目前正在开发一个 TDD 项目,并且作为一个优秀的 TDDer,我已经模拟/存根了所有内容,但是如果我可以指定测试结果的显示顺序 显示 - 主题而不是字母顺序,它将使其更具可读性。到目前为止,我唯一能想到的是将 a_b_c_ 添加到类、类、命名空间和方法中。 (不好)我认为 [TestOrderAttribute] 属性会很好 - 不是严格遵循框架,而是一个提示,以便我们可以实现这一点

【讨论】:

    【解决方案5】:

    不管测试是否依赖于顺序......我们中的一些人只想以一种有序的方式控制一切。

    单元测试通常是按照复杂性的顺序创建的。那么,为什么不应该按照复杂性或创建顺序来运行它们呢?

    就我个人而言,我喜欢看到测试按照我创建它们的顺序运行。在 TDD 中,每个连续的测试自然会变得更加复杂,并且需要更多的时间来运行。我宁愿先看到更简单的测试失败,因为它可以更好地指示失败的原因。

    但是,我也可以看到以随机顺序运行它们的好处,特别是如果您想测试您的测试对其他测试没有任何依赖关系。在“随机运行测试直到停止”中添加一个测试运行器的选项怎么样?

    【讨论】:

      【解决方案6】:

      我正在一个相当复杂的网站上使用 Selenium 进行测试,整个测试套件可以运行半个多小时,而且我还没有接近覆盖整个应用程序。如果我必须确保每次测试都正确填写之前的所有表格,这会为整个测试增加大量时间,而不仅仅是少量时间。如果运行测试的开销太大,人们就不会像应有的那样频繁地运行它们。

      所以,我把它们整理好,并依靠以前的测试来完成文本框等。当前置条件无效时,我使用 Assert.Ignore(),但我需要让它们按顺序运行。

      【讨论】:

      • 完全。我在同一条船上。
      • 我也是!正是我提出这个问题的原因!
      • @NiklasRingdahl 如果您将 Visual Studio 与 nunit 一起使用,请转储 nunit 并使用 MS 测试。然后您可以利用 Visual Studio 的有序测试文件按照您希望它们执行的顺序排列测试用例
      • @RahulLodha 谢谢!我会调查的。
      【解决方案7】:

      我真的很喜欢上一个答案。

      我稍微改了一下,可以使用属性来设置下单范围:

      namespace SmiMobile.Web.Selenium.Tests
      {
          using System;
          using System.Collections.Generic;
          using System.Linq;
          using System.Reflection;
          using NUnit.Framework;
      
          public class OrderedTestAttribute : Attribute
          {
              public int Order { get; set; }
      
      
              public OrderedTestAttribute(int order)
              {
                  Order = order;
              }
          }
      
          public class TestStructure
          {
              public Action Test;
          }
      
          class Int
          {
              public int I;
          }
      
          [TestFixture]
          public class ControllingTestOrder
          {
              private static readonly Int MyInt = new Int();
      
              [TestFixtureSetUp]
              public void SetUp()
              {
                  MyInt.I = 0;
              }
      
              [OrderedTest(0)]
              public void Test0()
              {
                  Console.WriteLine("This is test zero");
                  Assert.That(MyInt.I, Is.EqualTo(0));
              }
      
              [OrderedTest(2)]
              public void ATest0()
              {
                  Console.WriteLine("This is test two");
                  MyInt.I++; Assert.That(MyInt.I, Is.EqualTo(2));
              }
      
      
              [OrderedTest(1)]
              public void BTest0()
              {
                  Console.WriteLine("This is test one");
                  MyInt.I++; Assert.That(MyInt.I, Is.EqualTo(1));
              }
      
              [OrderedTest(3)]
              public void AAA()
              {
                  Console.WriteLine("This is test three");
                  MyInt.I++; Assert.That(MyInt.I, Is.EqualTo(3));
              }
      
      
              [TestCaseSource(sourceName: "TestSource")]
              public void MyTest(TestStructure test)
              {
                  test.Test();
              }
      
              public IEnumerable<TestCaseData> TestSource
              {
                  get
                  {
                      var assembly =Assembly.GetExecutingAssembly();
                      Dictionary<int, List<MethodInfo>> methods = assembly
                          .GetTypes()
                          .SelectMany(x => x.GetMethods())
                          .Where(y => y.GetCustomAttributes().OfType<OrderedTestAttribute>().Any())
                          .GroupBy(z => z.GetCustomAttribute<OrderedTestAttribute>().Order)
                          .ToDictionary(gdc => gdc.Key, gdc => gdc.ToList());
      
                      foreach (var order in methods.Keys.OrderBy(x => x))
                      {
                          foreach (var methodInfo in methods[order])
                          {
                              MethodInfo info = methodInfo;
                              yield return new TestCaseData(
                                  new TestStructure
                                      {
                                          Test = () =>
                                              {
                                                  object classInstance = Activator.CreateInstance(info.DeclaringType, null);
                                                  info.Invoke(classInstance, null);
                                              }
                                      }).SetName(methodInfo.Name);
                          }
                      }
      
                  }
              }
          }
      }
      

      【讨论】:

      • 我认为这种方法很棒。请注意,尽管反射代码将使用该属性提取所有方法,因此如果您尝试运行特定的测试夹具,您可能会惊讶地发现它运行得比您想象的要多。如果这不是所需的行为,您可以轻松地修改 LINQ 查询。有关更多信息,请参阅我的答案中的链接。
      • OrderedTest 在 NUnit 3 中不再支持。
      【解决方案8】:

      我知道这是一篇相对较旧的帖子,但这里有另一种方法可以让您的测试井井有条,而且不会让测试名称变得尴尬。通过使用 TestCaseSource 属性并让您传入的对象具有委托(Action),您不仅可以完全控制顺序,还可以将测试命名为它是什么。

      这是可行的,因为根据文档,从测试源返回的集合中的项目将始终按照它们列出的顺序执行。

      这是我明天要做的演示的演示:

      using System;
      using System.Collections.Generic;
      using System.Linq;
      using System.Text;
      using NUnit.Framework;
      
      namespace NUnitTest
      {
          public class TestStructure
          {
              public Action Test;
          }
      
          class Int
          {
              public int I;
          }
      
          [TestFixture]
          public class ControllingTestOrder
          {
              private static readonly Int MyInt= new Int();
      
              [TestFixtureSetUp]
              public void SetUp()
              {
                  MyInt.I = 0;
              }
      
              [TestCaseSource(sourceName: "TestSource")]
              public void MyTest(TestStructure test)
              {
                  test.Test();
              }
      
              public IEnumerable<TestCaseData> TestSource
              {
                  get
                  {
                      yield return new TestCaseData(
                          new TestStructure
                          {
                              Test = () =>
                              {
                                  Console.WriteLine("This is test one");
                                  MyInt.I++; Assert.That(MyInt.I, Is.EqualTo(1));
                              }
                          }).SetName(@"Test One");
                      yield return new TestCaseData(
                          new TestStructure
                          {
                              Test = () =>
                              {
                                  Console.WriteLine("This is test two");
                                  MyInt.I++; Assert.That(MyInt.I, Is.EqualTo(2));
                              }
                          }).SetName(@"Test Two");
                      yield return new TestCaseData(
                          new TestStructure
                          {
                              Test = () =>
                              {
                                  Console.WriteLine("This is test three");
                                  MyInt.I++; Assert.That(MyInt.I, Is.EqualTo(3));
                              }
                          }).SetName(@"Test Three");
                  }
              }
          }
      }
      

      【讨论】:

      • 我将这种模式用于并行集成测试,这种测试太耗时而无法线性运行。所有测试都针对一种类型,因此我使用 Action,并且每个 Action 都有一个键,说明“ShouldBeFoo”是做什么的。这样,测试所做的事情将在测试名称中可见,并且可以过滤 TestCaseSource,以便将不同类型的测试组合在一起。具有讽刺意味的是,我不关心执行顺序,但同意它会起作用。
      • 使用TestCaseSource 作为一种运行有序测试的方式是天才的一击。做得好。我采用了这种方法以及下面的方法,并添加了一些额外的修改以使其更易于使用。请参阅我的答案中的链接以获取更多信息,但基本想法来自这个很棒的答案!
      • 遗憾的是,对于 NUnit 3,TestCaseSource 的源必须是静态的,这排除了使用模式。无赖。
      • @康拉德。我看不出使方法静态或不静态有什么区别。无论哪种方式,测试仍然按顺序返回。
      • 不是方法需要是静态的——TestCaseSource 的源(变量或属性)必须是 NUnit 3 中的静态对象,否则测试不会执行。而且您不能在静态对象中创建动态对象。这就是它在第 3 版中不起作用的原因。
      【解决方案9】:

      我正在使用用 C# 编写的 Selenium WebDriver 端到端 UI 测试用例,这些测试用例使用 NUnit 框架运行。 (不是单位案例)

      这些 UI 测试当然取决于执行顺序,因为其他测试需要添加一些数据作为前提条件。 (每次测试都做步骤是不可行的)

      现在,在添加第 10 个测试用例后,我看到 NUnit 想要按以下顺序运行: 测试_1 测试_10 测试_2 测试_3 ..

      所以我想我现在必须将测试用例名称按字母顺序排列,但是将控制执行顺序的这个小功能添加到 NUnit 会很好。

      【讨论】:

      • 不同意 Arran :UI 测试本质上是小步骤的序列。每一步都应该是一个测试(原因 - 如果失败,我需要知道哪一步)。序列可以是独立的,但在一个序列中,顺序很重要,失败时停止是必要的。
      【解决方案10】:

      使用测试排序机制有很好的理由。我自己的大多数测试都使用良好的实践,例如设置/拆卸。其他则需要大量数据设置,然后可用于测试一系列功能。到目前为止,我已经使用大型测试来处理这些(Selenium Webdriver)集成测试。但是,我认为https://github.com/nunit/docs/wiki/Order-Attribute 上的上述建议帖子有很多优点。下面是一个为什么订购会非常有价值的例子:

      • 使用 Selenium Webdriver 运行测试以下载报告
      • 报告的状态(是否可下载)缓存 10 分钟
      • 这意味着,在每次测试之前,我需要重置报告状态,然后等待最多 10 分钟,然后确认状态已更改,然后验证报告下载是否正确。
      • 报告由于其复杂性,无法通过模拟或测试框架内的任何其他机制以实用/及时的方式生成。

      这 10 分钟的等待时间会减慢测试套件的速度。当您在大量测试中增加类似的缓存延迟时,会消耗大量时间。排序测试可以允许数据设置在测试套件开始时作为“测试”完成,依赖缓存的测试在测试运行结束时执行。

      【讨论】:

        【解决方案11】:

        通常单元测试应该是独立的,但如果必须,那么您可以按字母顺序命名您的方法,例如:

        [Test]
        public void Add_Users(){}
        
        [Test]
        public void Add_UsersB(){}
        
        [Test]
        public void Process_Users(){}
        

        或者你可以这样做..

                private void Add_Users(){}
        
                private void Add_UsersB(){}
        
                [Test]
                public void Process_Users()
                {
                   Add_Users();
                   Add_UsersB();
                   // more code
                }
        

        【讨论】:

        • 除了现在名称必须按字母顺序排列,这是一个糟糕的解决方案。 :(
        • @user166390 - 不是不可怕。它的工作原理取决于 NUnit 的记录行为。
        • ➕1 对我来说已经足够好了,如果你用a_ b_ t1_, t2_ 开始你的测试,或者依靠容易错过的尾随字符会更容易
        【解决方案12】:

        这个问题现在真的很老了,但是对于可能通过搜索找到这个问题的人,我从 user3275​​462 和 PvtVandals / Rico 那里得到了很好的答案,并将它们添加到 GitHub repository 以及我自己的一些更新。我还创建了 an associated blog post 以及一些其他信息,您可以查看更多信息。

        希望这对大家有帮助。此外,我经常喜欢使用 Category 属性来区分我的集成测试或其他端到端测试与我的实际单元测试。其他人指出,单元测试不应该有顺序依赖,但其他测试类型经常这样做,所以这提供了一种很好的方式来只运行您想要的测试类别并为这些端到端测试排序。

        【讨论】:

        【解决方案13】:

        如果您使用[TestCase],则参数TestName 提供测试的名称。

        如果未指定,则根据方法名称和提供的参数生成名称。

        您可以控制测试执行的顺序,如下所示:

                            [Test]
                    [TestCase("value1", TestName = "ExpressionTest_1")]
                    [TestCase("value2", TestName = "ExpressionTest_2")]
                    [TestCase("value3", TestName = "ExpressionTest_3")]
                    public void ExpressionTest(string  v)
                    {
                        //do your stuff
                    }
        

        这里我使用了方法名称"ExpressionTest" 带数字的后缀。

        您可以使用任何按字母顺序排列的名称 见TestCase Attribute

        【讨论】:

        • 完美!这就是我要找的东西!
        【解决方案14】:

        我很惊讶 NUnit 社区还没有想出任何东西,所以我自己去创造这样的东西。

        我目前是developing an open-source library,它允许您使用 NUnit 订购您的测试。您可以订购测试夹具和订购“订购的测试规范”。

        该库提供以下功能:

        • 构建复杂的测试排序层次结构
        • 如果按顺序进行的测试失败,则跳过后续测试
        • 按依赖项而不是整数顺序对测试方法进行排序
        • 支持与无序测试并行使用。首先执行无序测试。

        该库实际上受到 MSTest 如何使用 .orderedtest 文件测试排序的启发。请看下面的例子。

        [OrderedTestFixture]
        public sealed class MyOrderedTestFixture : TestOrderingSpecification {
            protected override void DefineTestOrdering() {
                TestFixture<Fixture1>();
        
                OrderedTestSpecification<MyOtherOrderedTestFixture>();
        
                TestFixture<Fixture2>();
                TestFixture<Fixture3>();
            }
        
            protected override bool ContinueOnError => false; // Or true, if you want to continue even if a child test fails
        }
        

        【讨论】:

          【解决方案15】:

          您不应该依赖测试框架选择测试执行的顺序。测试应该是独立和独立的。因为他们不应该依赖其他一些测试为他们设置舞台或在他们之后进行清理。无论测试的执行顺序如何(对于给定的 SUT 快照),它们还应该产生相同的结果

          我做了一些谷歌搜索。像往常一样,有些人采用了鬼鬼祟祟的技巧(而不是解决底层的可测试性/设计问题

          • 按字母顺序命名测试,以便测试按照它们“需要”执行的顺序出现。但是 NUnit 可能会选择在以后的版本中改变这种行为,然后你的测试就会被淹没。更好地将当前的 NUnit 二进制文件检入到源代码管理中。
          • VS(恕我直言,鼓励使用他们的“敏捷工具”的错误行为)在他们的 MS 测试框架中有一种称为“有序测试”的东西。我没有浪费任何时间阅读,但它似乎针对的是相同的受众

          另请参阅:characteristics of a good test

          【讨论】:

          • 在某些情况下,您希望在长时间运行的测试之前执行更快的运行测试,特别是在集成和验收测试中。例如,在博客应用程序中:您首先测试登录,因为如果该功能不起作用,则发布也将不起作用,因此执行该测试没有意义(您可以手动停止运行器)。但是,如果您尝试继续运行测试,则需要更多时间。
          • @MarcelValdezOrozco - 您可以通过不同的物理 dll 或使用标签/类别对测试进行分区来实现该目标。您可以创建构建脚本以按顺序运行 dll/类别。通常,允许对测试进行排序通常会导致耦合测试产生对相邻测试的依赖性(当然,随着时间的推移)。 AFAIR 在 NUnit 的下一个大版本中,它将支持不同的测试顺序,例如随机等
          • 进一步划分相同类型的测试(例如验收测试)是没有意义的,并且将它们分成另一个 DLL 会不必要地增加任何地方的复杂性:源代码、构建脚本、测试脚本、项目结构等. 只是对测试进行简单的重新排序。
          • 发射(基本上所有的模拟库)是顺序很重要的一个原因。您无法卸载您的应用程序域,并且 nunit 运行程序(如果在程序集中运行所有测试)至少在该“修复”中为所有测试保留该域。如果一个测试创建了一个类型来测试某些东西,而另一个测试由于顺序与该创建的类型冲突,那么这不是一个错误的测试。它们在逻辑上是隔离的,只是 nUnit 没有在每个“测试”本身之间提供适当的隔离。
          • 这是一个相当傲慢的答案,而且很有趣,因为它真的只适用于单元测试,完全忽略了务实。
          【解决方案16】:

          如果使用TestCaseSource,关键是override string ToString方法,它是如何工作的:

          假设你有 TestCase 类

          public class TestCase
          {
              public string Name { get; set; }
              public int Input { get; set; }
              public int Expected { get; set; }
          }
          

          还有一个测试用例列表:

          private static IEnumerable<TestCase> TestSource()
          {
              return new List<TestCase>
              {
                  new TestCase()
                  {
                     Name = "Test 1",
                     Input = 2,
                     Expected = 4
                  },
                  new TestCase()
                  {
                      Name = "Test 2",
                      Input = 4,
                      Expected = 16
                  },
                  new TestCase()
                  {
                      Name = "Test 3",
                      Input = 10,
                      Expected = 100
                  }
              };
          }
          

          现在让我们将它与 Test 方法一起使用,看看会发生什么:

          [TestCaseSource(nameof(TestSource))]
          public void MethodXTest(TestCase testCase)
          {
              var x = Power(testCase.Input);
              x.ShouldBe(testCase.Expected);
          }
          

          这不会按顺序测试,输出会是这样的:

          因此,如果我们将 override string ToString 添加到我们的类中,例如:

          public class TestCase
          {
              public string Name { get; set; }
              public int Input { get; set; }
              public int Expected { get; set; }
          
              public override string ToString()
              {
                  return Name;
              }
          }
          

          结果会改变,我们得到测试的顺序和名称,如:

          注意:

          1. 这只是说明如何在测试中获取名称和顺序的示例,顺序是按数字/字母顺序排列的,因此如果您有十多个测试,我建议进行测试 01、测试 02.... 测试 10、测试 11等等,因为如果您进行测试 1 并且在某个时候测试 10,那么顺序将是测试 1、测试 10、测试 2 .. 等等。
          2. 输入和预期可以是任何类型、字符串、对象或自定义类。
          3. 除了顺序之外,这里的好处是您可以看到更重要的测试名称。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2023-01-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-03-04
            • 2018-08-30
            • 2011-09-14
            相关资源
            最近更新 更多