Nunit: 每个测试用例的实例功能

创建于 2017-11-24  ·  112评论  ·  资料来源: nunit/nunit

每个人的心智模型似乎是在夹具中并行化时每个测试用例创建一个实例(https://github.com/nunit/nunit/issues/2105,https://github.com/nunit/nunit/issues /2252, https://github.com/nunit/nunit/issues/2573),尽管情况从未如此。 我倾向于通过始终使用静态类并定义一次性嵌套夹具类来解决这个问题,该类在某些方面提供更丰富的体验(示例),但这可能不是我们想要的每个人。 这里的一个小缺点是在术语上,静态类是 NUnit 认为是夹具的东西,但真正的夹具是嵌套类。

将每个测试用例设置为默认值不是一种选择,因为这会破坏依赖于一个测试能够从另一个测试访问状态的非并行装置。 使用[Order]属性的人可能是它唯一会影响的人,但它会完全破坏我能想到的任何依赖于订单的测试。

我喜欢查理的提议:

  • 一个装配级属性,允许将测试夹具的创建方式设置为单个实例或每个夹具实例。
  • 单实例会像现在一样工作
  • 每个夹具的实例将为每个测试创建一个新夹具
  • 要么为每个测试执行一次设置和拆卸活动(即不再是“一次”),要么导致 OneTimeSetUp 和 OneTimeTearDown 被标记为错误。

可能的扩展:

  • 也使这个类级别,允许单个装置具有不同的生命周期。
  • 具有第三个程序集级别设置,可以为包含一个或多个并行运行的测试的任何夹具自动运行单独的实例。 (很难总是提前知道)

然而,我会说先做基础。

现在,如果存在可变实例状态并且夹具与自身并行运行,那么它肯定是一个错误,因此我们可以有一个 Auto 选项,如果来自夹具的测试用例配置为运行,则切换到每个测试用例的实例并避免任何重大更改。 我认为 Auto 有可能令人困惑,但它也可能是最明智的默认设置,这样每个人都不会将[assembly: InstancePerTestCase]为例行程序。

我赞成使用护栏。 如果夹具为每个测试用例执行一个实例,则 OneTimeSetUp 和 OneTimeTearDown 应标记为错误。 也许除非它们是静态的?

done feature normal docs releasenotes

最有用的评论

我们有机会对此进行审查吗? 感觉真的很接近

所有112条评论

目前,您 __can__ 在并行测试之间共享状态,前提是 (1) 您在构造函数或一次性设置中设置状态,并且 (2) 在任何测试中都不要更改它。 如果状态在测试或设置中设置为常量,您甚至可以在某些情况下顺利通过,前提是您从未将其设置为任何其他值。 后者很讨厌,尽管我认为我们有一些 NUnit 测试可以做到这一点。

我的想法是我们不能很快改变默认值,如果有的话。 具有可变状态目前并不能保证一个错误。 如果您真的对其进行了变异,那只是一个错误,除非我们进行代码分析,否则我们不知道这一点 - 我们不要这样做!

如果您不在测试、设置或拆卸中改变任何实例状态,您就不会被破坏。 如果你这样做了,只要你的测试可以与它们自己并行运行,你就已经崩溃了。 你不可能没有我能想到的破碎。 如果我们认为为用户带来的复杂性值得“合理的默认值”结果,

你有一个观点,但我会继续主张这是一个潜在的扩展,我们现在不需要决定。 让我们反复执行此操作,而不是尝试现在就做出所有决定。

我和@CharliePoole 在一起,默认值必须保持原样并且不能删除,否则它可能会破坏想要这种行为的人。

可能是夹具级别属性来触发每个线程的实例,而不是所有线程共享的实例。

@BlythMeister别担心! 我们不考虑任何会导致现有代码开始破坏的更改。

这很有趣。 我认为每个测试用例实例比每个线程实例更容易让你思考。 将测试分配给线程的方式是不可靠的,所以我想不出比每个测试用例实例有什么好处。

是的,我使用术语线程来表示并行执行......这实际上是不准确的。
我很抱歉。

在 XP 术语中(我希望每个人都知道这始终是我的观点)我们有三个故事,它们相互建立...

  1. 用户可以为程序集中的所有装置全局指定每个测试用例的行为。

  2. 用户可以在夹具上指定每个测试用例的行为。

  3. 用户可以为包含可以并行运行的测试的装置指定每个测试用例的行为。

我希望看到 (1) 在这个问题中完全实现。 我认为我们应该暂时将 (2) 放在我们的脑海中——或者至少是编写代码的部分——。 我认为 (3) 是一种可能性,但我们在其他问题中已经遇到了一些陷阱。 (夹具不知道它是否会并行运行测试用例!)让我们谈谈 JustInTime,它将在 (1) 和 (2) 完成并合并之后。 FWIW,我认为我们有一种倾向,因为我们试图提前考虑几个动作,从而陷入困境。 我提到了 (2) 和 (3) 以便我们不会以任何方式阻止它们,这似乎很容易避免。

我想我们都同意了。 不过,在此之前我有一些 NUnit 优先事项,所以我会将它留给其他人评论并可能会选择。

使用属性而不是命令行设置这样做的好处在于它适用于任何跑步者。

我同意@CharliePoole它应该是一个属性。 设计测试时需要考虑每个测试的实例或每个夹具的实例,因此它们应该始终以这种方式运行。 我也更喜欢只做选项 1。我认为如果你想要那种风格的测试,你应该为你的整个测试套件选择它。 允许您在较低级别选择加入会引入复杂性,我认为这不会带来良好的投资回报率。

只是好奇是否有任何即将实施此功能的计划?

@rprouse这仍然被标记为需要设计,但看起来讨论已经涵盖了所有内容。

@CharliePoole同意这可以从设计转移。 @pfluegs除了设计它之外,我们还没有开始研究它,所以还没有计划。

不确定是否有人对它如此关注,但没有讨论或设计标签意味着有人可以做志愿者。 😄

我刚刚被这个问题严重咬了 - +1 修复。

如果我们想要更多选择,我们是否想要:

```c#
[装配:FixtureCreation(FixtureCreationOptions.PerTestCase)]

And:
```diff
namespace NUnit.Framework
{
+   public enum FixtureCreationOptions
+   {
+       Singleton, // = default
+       PerTestCase
+   }
}

至少在新框架的背景下,我自己对此的想法有所改变。

我现在认为应该有两个类级别的属性,每个行为一个。 我会让 TestFixture 在每个测试中使用一个实例,而 Shared Fixture 就像 NUnit 现在一样工作。 显然,这是 NUnit 上下文中的重大变化,但一般方法可能值得考虑。

@CharliePoole [TestFixture(FixtureOptions.PerTestCase)]似乎是一个不错的语法,但我也非常想要一个程序集级别的属性,以便我可以选择加入每个测试项目。 我们希望程序集级属性看起来像什么?

是的,这就是为什么我表达了我在新框架上所做的保留 WRT。 但是,在 NUnit 的情况下,您不会想要更改TestFixture的语义,而且我想不出一个非常好的名称来表示实例化行为。 如果您有两个名称,那么要在所有灯具上获得所需的行为,您所要做的就是对源文件进行全局编辑。

我不喜欢程序集级别的全局设置,因为它们最终会使一些开发人员感到困惑,他们可能不知道 AssemblyInfo 文件中的设置。 目前,我们确实允许使用一些,所以我认为添加另一个没有太大的害处。 我对装配级属性的名称没有任何想法,只是它应该传达我们正在为装配中的所有装置设置一个选项(默认?)。

由于您提到的原因,我在某种程度上也不喜欢程序集级别的默认值。 与为每个当前无属性的装置类添加新属性相比,程序集级别的属性是我更喜欢的路径。

@nunit/framework-team 你怎么看? 对于该功能的初步介绍:

  • 是/否装配级设置?
  • 是/否灯具级设置?
  • 是/否方法级设置?
  • 是/否测试用例级设置?

关于无属性夹具类的好点。

我会说是的,是的,不,不

我同意查理 - 澄清“夹具级别”意味着“类级别”。 (与例如测试用例源方法相反。😊)

这有帮助! 所以[TestFixture(FixtureCreationOptions.PerTestCase)]是在同一个类上允许多个 TestFixture 属性,然后它们可能会相互矛盾。 拥有适用于程序集和类的新命名属性可能更好更简单?

```c#
[装配:ShareFixtureInstances(false)]

[ShareFixtureInstances(false)]
公共类 SomeFixture
{
// ...
}

Framework API:

```diff
namespace NUnit.Framework
{
+   [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
+   public sealed class ShareFixtureInstancesAttribute : PropertyAttribute
+   {
+       public bool ShareFixtureInstances { get; }

+       public ShareFixtureInstancesAttribute(bool shareFixtureInstances);
+   }
}

听起来不错。 这个的继承行为是什么——如果我在基类上有属性,有自己的测试,然后也从那个类继承? 😊

据推测,因为它是一个“类”属性 - 它必须在运行有问题的测试的实际类上?

另外,我并不完全喜欢这个名字——因为在 nunit 术语中,fixture 并不总是一个类,我不认为(例如 testcasesource 中的所有测试——除非 'suite' 和 'fixture' 具有更强的语义意思比我想象的要好,用 Nunit 术语来说!)

像 InstancePerTest 那样的东西怎么样......但更好地命名......

同意程序集和类级别,但没有更深入。 我也更喜欢@ChrisMaddock的建议ClassInstancePerTest(bool) 。 我还假设类级别的属性将覆盖程序集级别,并且我希望它被继承。 我认为继承很重要,因为您通常希望每个测试都有一个基于类成员并结合并行性的实例。 因此,您希望将其自动应用于派生类。

我曾经感到非常惊讶。当测试夹具类型可以作图NUnit的每次测试创建一个实例。 我一直认为单元测试就像,我不知道,单元测试:你设置脚手架,运行测试,然后脚手架消失了。 测试之间没有无意的状态转移。

学了那个。 我和@jnm2 一样,手动实例化测试。 但是当测试例如创建和删除文件时,case runner 变得非常麻烦:它们必须是 IDisposable; 再也没有神奇的[TearDown]了。 :( 也没有一致的方法从脚手架错误中判断测试错误。这是一个非常痛苦的用例。

如果我可以介入设计,我会和

名称PerTestInstanceInstancePerTest或类似名称听起来比ShareFixtureInstances好得多。 我有点不喜欢属性名称中的Class一词,因为当我测试 F# 代码时,我不认为类型实例是classes 。 但可以肯定的是,我可以忍受! :)

哦,孩子,等不及这个功能了!

@kkm000我同意使用“类”。 NUnit 通常使用与实现不同的名称。 即便如此,人们也会做出假设,但最好始终如一地避免此类事情。

我正在谈论的示例:Test、TestFixture、SetUpFixture、Worker(不是线程)等。原则上,类 __could__ 代表单个测试,而不是夹具......我不是说我们这样做,甚至应该,但__我们可以__。

@kkm000

感谢您的评论! 你根本没有闯入。 我们的设计目标是尽可能与语言无关。
除了对 C# 的关注之外,关于class一词的另一件事是:如果有一天我们支持不是类的装置会怎样?

[InstancePerTestCase]会回避这个问题,但它可能会失去直观的清晰度。

今天的fixtures 和classes 并没有太大的不同,但是如果它们确实有不同,我很确定这个属性与fixtures 相关,而不是class。

套件是一个通用的组织概念,而夹具是保存设置/拆卸逻辑和测试状态并传递给测试方法的东西(当前作为实例方法的this参数)。 固定装置不必是组织层次结构的一部分。 让我们想象一下,fixtures 与类的概念和测试套件的概念不同:

```c#
public static class SomeTestSuite // 不是夹具:没有设置/拆卸逻辑,也没有测试状态
{
[Test] // 单个测试,不是参数化套件;
// 夹具通过参数而不是通过隐式的 this 参数注入
public static void SomeTest(SomeFixture fixture)
{
fixture.SomeService.SomeProperty = true;
夹具.SomeAction();
夹具.AssertSomething();
}
}

[测试夹具]
公共结构 SomeFixture : IDisposable
{
公共 SomeService SomeService { 获取; } // 状态

public SomeFixture() // This was almost legal syntax in C# 6, but I'm making a point 😁
{
    // Setup
}

public void Dispose()
{
    // Teardown
}

public void SomeAction()
{
    // Helper method
}

public void AssertSomething()
{
    // Helper method
}

}
``

在上面的例子中, [FixtureInstancePerTestCase][SharedFixtureInstances(false)]不关注类,也不关注层次结构(套件):它关注固定装置,固定装置是可重用的设置/拆卸逻辑和状态。 具体来说,它关注是否为每个测试用例创建一个新的SomeFixture ,或者是否将相同的一个传递给每个测试。

由于这个属性会控制不同的 ITests 是否会共享 ITest.Fixture 实例,并且被控制的东西是我们用[TestFixture]装饰的东西(另一个名称中带有Fixture属性),它看起来像一个_consistent_ 选项以使用单词 Fixture。

(我带来了一些似乎很重要的讨论点,但我并没有特别关注这个名字最终是什么。)

@jnm2套件和夹具的概念是可分离的,这是完全正确的。 然而,NUnit 遵循了创建时存在的所有 xunit 测试框架的示例,将两者结合起来。 它在使夹具共享方面偏离了其他框架。

据我所知,xunit.net 是第一个(唯一的?)框架,它使夹具成为独立于测试继承层次结构的实体。

当我创建参数化测试时,我可以将 TestCaseSource 作为单独的“夹具”引入。 我没有这样做,我认为重做它所需的更改现在会中断。

我认为人们偶尔(在并行之前)询问实例行为的原因是其他框架所做的。 出于类似的原因,他们__没有__要求可分离的固定装置。

所以,总结一下,这就是我的想法......

  1. 我们需要每个测试用例行为的实例。

  2. 由于向后兼容,它不能成为默认值。

  3. 一个可分离的装置是一个很酷的想法,但也是一个单独的问题。

  4. 可分离的装置看起来至少与每个实例装置一样复杂。 当然,在这种情况下,您不必处理向后兼容性,这会更容易。

只是为了确认第 3 点和第 4 点,我的代码示例不是作为对新功能的建议,而是作为一个思想实验,旨在帮助我们为该属性找到正确的名称。 思想实验告诉我,我们真正关注的是固定装置的想法,而不是课程或套件,即使它们现在恰好重合。

无论它们采用什么形式,夹具都是设置/拆卸/状态的可重用定义。 我相信这就是这个词的意思。 夹具实例(设置/拆卸/状态的发生)是我们的新属性决定在测试用例之间共享或不共享的内容。 我认为名称中的FixtureInstances会带来最大的清晰度。

这对每个人来说都是正确的吗? 如果不是,我们还可以考虑哪些其他方面?

啊! 抱歉,我以为你说得很好,通常被忽视了,但我也担心我们可能会跑题。 :slightly_frowning_face:

我认为全名(无论你输入什么)都应该包括 Fixture 和 Instance。 它是如何完成的取决于您是在谈论属性名称还是在构造它时使用的枚举。 不确定您是否已经选择了其中之一。

是的,好点。

如果我们进行枚举,则输入时间会更长。 这对于面向类的属性比面向程序集的属性更重要。 我们还应该避免将枚举作为参数添加到TestFixtureAttribute因为在同一个类中允许多个 TestFixtureAttributes,并且一个的参数可能与另一个的参数相矛盾。 我们可以通过测试时错误来处理这个问题,但是让无效状态变得不可表示通常很好。

让我们设置一些选项,然后我们可以从那里进行调整。
我们正在寻找能够在直观性、与 NUnit 风格的一致性以及适当的灵活性之间取得平衡的东西。 (在大多数情况下,这些东西应该已经重叠了。😄)


提案A

```c#
[装配:FixtureOptions(InstancePerTestCase = true)]

[FixtureOptions(InstancePerTestCase = false)]
类 SomeFixture
{
}

Pro: extending later if there's ever more options for fixture handling (doubtful?)
Pro: two names so there's less stuffed into a single name
Con: `[FixtureOptions]` would be legal but highly misunderstandable syntax. (Does it reset the assembly-wide setting to the default, `false`, or does it have no effect, e.g. `null`?)


### Proposal B

```c#
[assembly: FixtureOptions(FixtureCreationOptions.PerTestCase)]

[FixtureOptions(FixtureCreationOptions.Singleton)]
class SomeFixture
{
}

public enum FixtureCreationOptions
{
    Singleton, // = default
    PerTestCase,
    // Doubtful there will be other options?
    // Maybe Pool, where we reset and reuse between nonconcurrent tests?    
}

优点:无效状态无法表示
优点:如果有更多的夹具处理选项,可以稍后扩展(值得怀疑?)
缺点:在键入属性名称和枚举名称方面存在冗余

提案 C

```c#
[装配:FixtureInstancePerTestCase]

[FixtureInstancePerTestCase(false)]
类 SomeFixture
{
}
``

优点:无效状态无法表示
亲:很难误解(但我在这里有一个盲点,因为我不是新用户)
骗局:长名字
也许骗局:非常具体的名称,可以指定选项和值。 (但我们也有我喜欢的[NonParallelizable] 。)


(欢迎其他建议!)

有两个属性值得一提:一个用于程序集,另一个用于类本身。

优点:更容易实现两个简单的属性,而不是在上下文中具有不同行为的一个属性。 (从 ParallelizableAttribute 吸取的教训)
优点:用户可能更容易理解,因为每个都可以适当命名。 例如,程序集级别可以包括“默认”一词。
缺点:要记住两个属性。

使用每种替代方法的方法:

```C#
[装配:DefaultFixtureOptions(InstancePerTestCase = true)]

[FixtureOptions(InstancePerTestCase = false)]
类 SomeFixture
{
}

[装配:DefaultFixtureOptions(FixtureCreationOptions.PerTestCase)]

[FixtureOptions(FixtureCreationOptions.Singleton)]
类 SomeFixture
{
}

[装配:DefaultFixtureInstancePerTestCase]

[FixtureInstancePerTestCase(false)]
类 SomeFixture
{
}
``

其他的建议:

  • 我喜欢 A 和 B 胜过 C
  • 你不应该把共享装置行为称为“单例”,因为它不是一个。 :smile: 如果您继承,您将获得基础装置的多个实例。
  • 如果您在装配体和夹具级别使用单独的属性,您实际上可以从夹具级别的名称中删除夹具。
  • 我们根本没有指定 SetUpFixtures 如何适应这个。 它们是有问题的,如果在它们上使用该属性,则可能需要运行时错误。 事实上,完全独立的属性的一个优点是我们不必处理这个问题。 (这也是 TestFixture 属性的一个优势,顺便说一句)
  • 我们需要警告人们他们每个测试用例的实例不适用于静态类。 这对我们所有人来说都是显而易见的,但有些人不会立即理解它。

顺便说一句,我经常希望我们在程序集和夹具级别使用 DefaultTestCaseTimeOut 并将超时仅保留在方法中。

好的,感谢您提出这种可能性。

现在让我烦恼的是,这在理论上是有道理的,即使我们没有朝这个方向发展:

c# [DefaultFixtureOptions(InstancePerTestCase = true)] class SomeFixture { [FixtureOptions(InstancePerTestCase = false)] public void SomeTest() { } }

因为实际执行的级别不是夹具级别本身,而是测试用例级别。
例如:派生夹具可能会覆盖基夹具的设置,但它真正做的只是影响派生类生成的每个测试用例的设置。

或者:

```c#
[装配:DefaultFixtureOptions(InstancePerTestCase = true)]

// 为什么这也不应该被视为默认值?
[DefaultFixtureOptions(InstancePerTestCase = true)]
公共抽象类 BaseFixture
{
}

公共密封类 SomeFixture : BaseFixture
{
}
``

我们如何提出何时以及何时不使用Default的理由?

我认为在每个测试用例上实现这个属性会带来很大的复杂性,但收益很小。

对于“普通”测试装置,我们将继续将实例实例化为与测试装置相关联的命令链的一部分。 例如,每个测试夹具,我们不会在那里实例化它,而是必须实现一个新命令作为测试链的一部分,这将与现在在夹具链中所做的几乎相同。

如果夹具可能包含两种类型的测试,那么我们将必须执行两次,每个模型一次。

好处似乎很小,因为用户可以通过将测试拆分为单独的装置轻松实现相同的目标。 毕竟,夹具是 NUnit 工件,要求用户将需要相同夹具行为的测试放入同一个夹具中是合理的。

关于何时使用“默认”...在我看来,您 __not__ 将在任何 TestFixxture 上使用它,而您只会在程序集级别使用它。 基类也不例外,因为属性实际上是通过继承在派生类上找到的。 因此,在基础上设置特定行为不是默认设置。 相反,它是该类以及从它派生的任何类的设置。 矛盾的规范应该导致错误或被忽略,就像我们在当前所有继承属性的情况下所做的那样。 也就是说,将属性 A 放在基类上,将属性 B 放在派生类上,与将 A 和 B 直接放在基类上没有区别。 我们这样设计它是因为用户做任何其他事情都会令人惊讶。

WRT 设置装置,顺​​便说一句,我认为它应该生成运行时错误以在那里指定任一实例化选项。 SetUpFixtures 不是 TestFixtures。

也就是说,将属性 A 放在基类上,将属性 B 放在派生类上,与将 A 和 B 直接放在基类上没有区别。 我们这样设计它是因为用户做任何其他事情都会令人惊讶。

哦,天哪,我原以为[Apartment][Parallelizable]会替换基类或基方法的设置!

这一切对我来说都很有意义。

我们可以排除想要一个枚举,例如FixtureCreationOptions吗? PerTestCasePerFixture ,假设的ReusedPerTestCase

我倾向于以下简单性:

```c#
[装配:DefaultFixtureOptions(InstancePerTestCase = true)]

[FixtureOptions(InstancePerTestCase = false)]
类 SomeFixture
{
}

Hypothetical other creation options would then be added this way:
```c#
[FixtureOptions(InstancePerTestCase = false, ReuseInstances = true)]

@nunit/framework-team 你有什么偏好?

关于

[assembly: DefaultFixtureOptions(InstancePerTestCase = true)]

也许只是我,但通常我将属性理解为表明标记对象具有某种特征,而不是其他特征; 属性的参数(如果有)正在改进所述特征。 以 Serializable 为例——对象是可序列化的,但如果未修饰,则不是; InternalsVisibleTo--内部对特定事物可见,参数对此进行了优化,否则内部对任何事物都不可见,等等。一些属性不遵循模式,但这些通常是低级别的,例如,CompilationRepresentation、MethodImpl。 这些通常带有必需的构造函数参数。

现在考虑
c# [assembly: DefaultFixtureOptions]
这是一个格式良好的 C#,因为对InstancePerTestCase的赋值是可选的。 它在语义上表达了什么?

对我来说, [assembly: DefaultFixtureInstancePerTestCase]似乎更自然(除了它的 sesquipedalian 名称,但这只是一种意见,在美学上是不合理的)。 一种属性,一种特质。 也许这个不需要任何参数或可选的可设置属性。

此外,作为用户,我发现有两个名称不同的属性令人惊讶,一个仅适用于程序集级别( DefaultSomething ),另一个仅适用于类型级别(仅Something ) . 自然,我不明白@CharliePoole提到的关于 Parallelizable 属性的“吸取的教训”是什么,但从岛的这一边,一对属性来了解一个人会做什么(在这个用户的肤浅想法中)似乎令人困惑。

@kkm000我同意您对表达单个特征的属性的评论。

WRT 在不同级别的“相同”特征的两个不同属性,我 __would__ 同意,如果我认为它们实际上是相同的。 但我不认为他们是。 我将以超时为例。

如果 Timeout 放在测试方法上,则表示“如果运行时间超过指定的毫秒数,则此测试方法将超时。” 但是,当 Timeout 被放置在夹具或组件上时,它具有不同的含义:“此项中包含的所有测试用例的默认超时时间为指定的毫秒数。” 当然,该默认值可能会在较低级别被覆盖。 两个相当不同的含义,顺便说一下,它们有两个不同的实现。 第一个将超时应用于它出现的测试。 第二个将默认值存储在测试上下文中,当遇到包含的测试用例时,它将被找到并用作默认值。 ParallelizableAttribute 的工作原理类似。

在这个特殊情况下,放置在夹具上,该属性的意思是“每个测试用例实例化这个夹具一次”。 在装配级别,它意味着“除非另有说明,否则每个夹具在此装配中实例化测试夹具一次”。

在这个特殊情况下,放置在夹具上,该属性的意思是“每个测试用例实例化这个夹具一次”。 在装配级别,它意味着“除非另有说明,否则每个夹具在此装配中实例化测试夹具一次”。

假设你在最后一句话中说错了 - 是 _“除非另有说明,否则每个 ~fixture~测试用例在这个程序集中实例化一次

假设我不是,对我来说,作为一个用户,语义看起来没有什么不同:“在属性标记的范围内为每个测试实例化一个类”,可以用“除非它的内部范围被标记为不同,如果有确实是这样一个内部范围,“无论是固定装置还是组件。 只是我的意见,但两个属性看起来确实有点混乱。 也许这只是我的思维方式,我真的不知道。

两个独立的实现

在独角兽和彩虹的 gedankenworld 中,实现不会影响面向用户的一面。 但是我们(你是 NUnit 的设计师,我也是它的用户之一)都是工程师,而工程就是妥协。 如果对不同的范围使用一个属性实际上会导致内部混乱和维护负担,我根本不会抱怨。 :)

最后,对我来说这是一个小问题,我对相同的属性没有强烈的感觉,并且可以接受任何一种排列的属性。

@kkm000是的,我打算“每个测试用例一次”。

我同意,如果您将其视为一系列嵌套的作用域,这一切都非常有意义。 然而,经验表明,用户要么 (a) 并不总是这样想,要么 (b) 没有正确理解范围的定义。

作为最后一个例子,考虑嵌套类和基类。 出于测试目的,它们都不会被 NUnit 视为嵌套的“范围”,但它们对用户来说似乎是这样。

我承认我在 Timeout / DefaultTimeout 中提到的问题更严重一些,因为夹具上的超时可能很容易意味着超时适用于整个夹具,这当然不是。 就此而言,程序集上的超时可能适用于整个程序集,而事实并非如此。

在这种情况下,似乎没有什么歧义,因为我们在名称中使用了“每个测试用例”,因此我可以轻松地采用任何一种方式。 反正我不工作。 :微笑:

想想看,如果 Timeout 被称为 TestCaseTimeout 会更好!

我很满意一个[FixtureInstancePerTestCase] 。 似乎对用户的心理负担最小。

@nunit/framework-team 你能帮我们确定一个方向吗,从https://github.com/nunit/nunit/issues/2574#issuecomment -426347735 浏览下来?

[FixtureInstancePerTestCase]也是我最不喜欢的选项。 😄 缺乏可扩展性是唯一的缺点。

我不喜欢[FixtureOptions]问题 - 我认为这太令人困惑了。 我个人更喜欢程序集和类的单个属性 - 我很欣赏这在 Timeout 和 Paralellizable 之前给我们带来了问题 - 但是当这个名字中有“Fixture”时,对我来说似乎不那么模棱两可。

我个人引用喜欢 enum 选项......除了我想不出如何适当地命名以使其更简洁!

有任何更新吗?

任何更新都将发布到此问题。

请问有人可以解决这个问题吗??
目前我们必须像这样包装我们所有的测试代码:
csharp [Test] public void TestExample(){ using(var tc = new MyTestContext()){ //code goes here } }
使用此功能,我们可以在设置时初始化测试上下文并在拆卸时处理,并且仍然是线程安全的。

你好,我们公司也遇到了这个问题。 我们正在从支持此功能的框架 (Py.Test) 迁移,所以这有点麻烦。 如果您能向我们展示控制此实例化的源代码,我们将不胜感激,以便我们可以对它进行分叉并进行必要的更改。

@MChiciak
如果这个问题不会很快得到解决,我们正在考虑迁移到 xUnit,因为在 xUnit 中这是默认行为:“xUnit.net 为每个运行的测试创建一个测试类的新实例” https://xunit .github.io/docs/shared-context

@LirazShay@MChiciak我也遇到了这个问题,但同意@CharliePoole的观点。 所以我制作了在 SetUp [ThreadStatic]初始化的特定于测试的字段,它工作正常。

@dfal
感谢您提供好的解决方法!

我会喜欢这个。 我目前有一个现有的测试套件,其中包含在 nunit 上运行的数千个测试,由于共享状态,无法轻松并行化。 如果提供此选项,我们将能够更轻松地并行化我们的测试套件。

作为一个思想实验,想象一下我们只是改变了 TestFixture 的工作方式。 这会打破什么?

  1. 任何使用排序并依赖于一个测试所做的状态更改来影响其他测试的测试。 在(据称)独立单元测试的情况下,这是一种非常糟糕的做法。 我们一直警告人们不要这样做,我不介意破坏任何依赖于以这种方式拥有单个实例的测试(当然还有消息)。

  2. 根据一次性设置中完成的操作,单个灯具实例可能无法并行运行。 这很讽刺,因为改变的动机是让装置并行运行! 对于需要该功能的人来说,这不是一个重大变化,因为他们已经无法并行运行,但对于那些已经依赖并行运行测试的人来说,这是一个重大的突破性变化 __after__ 不可并行化的“OneTimeSetUp”。

  3. 任何装置级别的OneTimeSetUp现在都将运行多次。 这将消除一次性设置带来的任何效率提升,并且在某些情况下可能会更改语义。 特别是,如果初始化以某种方式影响全局状态(例如设置数据库),它可能会导致错误。 这对我来说似乎是一个不可接受的破损。

我发现这个思想实验在思考如何实现夹具的每个测试用例实例化时很有用。 如果在 NUnit 3 中引入,它显然需要是一个新的非默认功能。对于 NUnit 4 或任何其他新框架(例如 TestCentric),新行为很容易成为默认功能。

作为注释,我非常简单地分别用TestFixtureTest替换了一些 MSTest 的TestClassTestMethod ,并发现 NUnit 和 MSTest 在这里有不同的语义。 MSTest 为每个测试创建新实例,我相信阅读本文的每个人都知道,NUnit 重用了 TestFixture 实例。

如果我能写TestFixture(lifeCycle = OneTestPerInstance)]或类似的东西来简化这种迁移,那就太好了。

_edit:_ 实际上,刚刚将测试移植到使用Setup方法后,事实证明现有的代码初始化器/之前/拆卸代码在 MSTest 下受到了相当大的折磨,现在是简单的属性初始化器构造函数,其中更好,更完整,所以我很高兴我做了翻译。 尽管如此,就兼容性而言,拥有它会很好。

@查理普尔

任何使用排序并依赖于一个测试所做的状态更改来影响其他测试的测试。

啊,是的,打破,打破这些测试,拜托了!!! 🤣 这是我能想到的对单元测试框架最严重的滥用。

任何装置级别OneTimeSetUp

呃,我一直认为这是每个进程运行一次......我一直想知道为什么它不是静态的。

但是,是的,性能也是一个非常有效的观点。

这个功能已经讨论了 2 年多了,我的意思不是听起来很冒犯,但是在最近的将来是否有任何具体和现实的计划来实际实施它?

我想知道,我的团队是否应该计划迁移到另一个框架,或者这是否会在未来几个月内得到解决,因为这个问题对我们来说是个难题。

这是对测试运行方式的重大更改,需要考虑全部影响,以免完全破坏那些依赖当前功能的人。

“解决”这个问题的最好方法是使您的测试类线程安全。 就像任何好的多线程应用程序一样,线程安全应该是最重要的。
例如,如果您使用在 setup 方法中设置然后在许多测试方法中使用的示例字段,则您的类不是线程安全的。 这不是 NUnit 的缺陷。 您已经编写了一个非线程安全的单例。

通过在您的 TestFixture 中创建一个子类,其中包含您作为每个测试的一部分创建的上下文信息,然后您确保您的单例 TestFixture 是线程安全的......因此......你“解决”了这个问题。
因此,与其花时间“迁移到另一个框架”,不如我建议您花时间使您的单例 TestFixtures 线程安全……您可能会发现这是一个更快的练习。

例如,当我第一次遇到 NUnit TestFixture 是一个单例的事实时,我将我的 100 多个测试装置转换为线程安全的(并且必须从 RhinoMocks 移动到 Moq,因为 RhinoMocks 也不是线程安全的)并且这需要我大约 4 周(老实说,Rhino -> Moq 花了大部分时间!)

原则上你是 100% 正确的,但是当你有一个现有的大约 7000 多个单元测试的测试套件时,仅仅将它们重写为线程安全是一项巨大的投资。

不确定这是否比转移到另一个框架更像是一项投资(可能有一些工具可以自动化)但我认为原则上不应放弃此功能请求,IMO

我并不是主张不应查看此功能。

但是 nunit 工作方式的如此大的变化,在我看来应该考虑向后兼容(这可能非常困难)或新的主要版本(也不是一个小任务)

我建议让 nunit 的用户编写线程安全的代码以在多线程环境中运行是一种更简单的操作形式。 你甚至可以编写脚本。

我不是这个 repo 的贡献者,所以我没有任何关于它是如何实现的上下文。 因此,这可能非常天真,但是否可以将其作为可选开关来实现? 所以这不会破坏任何东西?

整个问题正在讨论可以实现可选开关的各种方法......有很多方法

这是一个完全由志愿者组成的开源项目。 一旦一个功能被接受,就像这样,它会等待团队中或外部的某个人来选择它并执行它。 没有“老板”可以将工作分配给任何人。 如果他们没有时间致力于完成它,那么没有人这样做实际上是一件好事。 这样,任何有能力和有兴趣的人都可以使用它。

PR 总是受欢迎的,但这可能不是某人的第一次贡献,因为它有可能破坏 NUnit 中的许多其他东西。 反正我的意见。

我不再像过去那样积极地做出贡献,所以我对我认为应该如何做的评论被作为对任何人的建议。

我同意它已经存在了一段时间,我很乐意看到它自己完成。

@BlythMeister我认为 switch 把它简化了一点——你可能知道,但其他人可能不知道。

当然,这是一个开关,但它不仅仅是打开灯泡。 这更像是将收音机从 AM 切换到 FM ……全新的电路开始发挥作用。

在这种情况下,所涉及的是执行测试的全新生命周期,因此必须替换相当数量的代码。 不是小事。 风险在于确保新“电路”不会以任何方式影响旧“电路”。

对不起,你的权利,我的解释含糊其辞,为什么“开关”并不简单!

@BlythMeister我实际上完全按照你的描述做了——在测试期间创建了一个一次性对象来跟踪状态,然后在测试完成后处理它——来驱动我的 Selenium 测试工具。 这种方法的一个主要问题是国家不会坚持拆除。 因此,不可能对失败的测试(抓取屏幕截图、日志或其他状态)有条件地执行操作,因为测试状态不会在测试方法本身内部解析,只能在拆卸期间进行评估。 因此,虽然状态对象是使测试线程安全的强大方法,但它仅在您不需要在拆卸期间对测试结果进行处理时才有效。

让我记住使用测试结果做的事情不是我们设计的 TearDown 是有用的。 当然,我知道很多人都这样使用它,我理解动机,但有很多棘手的极端情况基本上归结为 TearDown 是测试的一部分,直到它结束才结束。

在 NUnit 3 中,我们希望看到更多用户使用引擎扩展检查框架外的结果,但在测试本身内工作似乎更熟悉和更容易理解。

@查理普尔

  1. 任何装置级别的OneTimeSetUp现在都将运行多次。 这将消除一次性设置带来的任何效率提升,并且在某些情况下可能会更改语义。 特别是,如果初始化以某种方式影响全局状态(例如设置数据库),它可能会导致错误。 这对我来说似乎是一种不可接受的破损。

为什么我们不能实现 Instance-per-test-case 功能并且仍然只运行 OneTimeSetUp 一次(让主线程记住调用它或其他什么)?

因为它会破坏初始化实例的任何现有 OneTimeSetUp,这很常见。

我认为这是任何基于交换机的解决方案中的一个问题。 如果我们为测试装置定义一个全新的替代方案,那么问题就不大了,我觉得这是一种非常诱人的方法。

因为它会破坏初始化实例的任何现有 OneTimeSetUp,这很常见。

当启用“每个测试用例的实例”功能时,只允许静态OneTimeSetUpOneTimeTearDown方法怎么样? 这将强制在这些方法中初始化的成员是静态的,因此显然在夹具实例之间共享。

虽然这个 Github 问题的主要动机是并行化,但我想抛出_最小意外原则_和_避免由相互影响的测试引起的错误_作为动机。 (如果我错过了其他人已经提到这一点,请道歉。)

如果您在这个相当旧的线程中没有回读足够多的内容,我的评论不会反对该功能,而是反对建议的特定实现。 我更喜欢用一种全新的夹具替换 TestFixture,它的工作方式不同。

我认为我最不惊讶的校长在这里工作得很好。 如果新灯具的工作方式不同,这并不奇怪。

另一方面,如果我们创建一个控制现有装置的标志,我可以想到当前实现中我们需要在该标志上分支的五六个不同的地方。

这并不是说这两种夹具类型可能不共享某些代码。 但这超出了用户的视野。

@fschmied顺便说一句,如果我们正在做一个新框架(或者甚至 NUnit 4),那么我会提出不同的观点......我认为我们应该让 NUnit 2 和 3 中的测试更难相互干扰。但是,只要我们在 NUnit 3 中进行更改,我认为兼容性规则。

我更喜欢用一种全新的夹具替换 TestFixture,它的工作方式不同。
[...]
我认为我最不惊讶的校长在这里工作得很好。 如果新灯具的工作方式不同,这并不奇怪。

你当然是对的。 我的意思是,对于原始类型的测试装置,这种行为仍然会让大多数人感到惊讶。 (两周前,一位同事碰巧发现了一个测试影响另一个测试的案例。他从 2013 年开始使用 NUnit,这就是我查找这个问题的原因。)

但当然,这是向后兼容性、实现复杂性和纠正原罪的愿望之间的权衡 [1]。

回到OneTimeSetUp :我认为每个测试装置都有一个OneTimeSetUp仍然很重要,即使每个测试都实例化了测试装置类。 为了避免在OneTimeSetUp操纵实例状态的情况下混淆,我建议使用 instance-per-test 功能强制OneTimeSetUp (和OneTimeTearDown )为静态。 (无论它是作为开关实现还是作为一种新型测试夹具实现。)


[1] 詹姆斯·纽柯克写道

我认为我们编写 NUnit V2.0 时最大的失误之一是没有为每个包含的测试方法创建测试装置类的新实例。 我说“我们”,但我认为这是我的错。 我不太明白在 JUnit 中为每个测试方法创建一个新的测试装置实例的推理。

不过,他也写了这个:

[...] 现在很难改变 NUnit 的工作方式,太多人会抱怨 [...]

:)

是的,吉姆和我多年来一直在争论这个问题。 :smile: 我们同意的一件事是最后一句话。 在实施他喜欢的方法之前,Jim 开始做一个新的框架。 我基本上也在做同样的事情,而 SetUp/TearDown 是我可能会以不同方式做的一件事。

我一直很惊讶有多少相当有经验的 NUnit 用户不知道所有测试用例都使用相同的实例。 可能是在 NUnit 刚出来的时候比较广为人知,因为它和 JUnit 有明显的区别。 现在这一切都被遗忘了,新人带着 NUnit 不匹配的期望来到 NUnit。 有什么要考虑的。

所以..这是可以争夺的吗? 还是还在设计阶段?

现在在 .NET 中使用没有 TestContext 的 XUnit 或 NUnit,使用样板并行测试

该功能被接受并被分配一个正常的优先级。 没有人被分配给它,它也没有列在里程碑中。 这意味着必须有人决定他们想从事这项工作。 那可能是提交者或贡献者。

我们通常不进行设计阶段——如果我们想要一个,就会应用设计标签——但即便如此,无论谁这样做,最好发表评论,描述他们计划如何实施它,特别是它的外观用户。 如果您从编码开始,无论如何您可能最终会参与设计讨论,因此最好先解决这个问题。

那么,你愿意为此工作吗?

当然,我刚开始工作
https://github.com/avilv/nunit/tree/instance-per-test

我是这个代码库的新手,所以我尽可能地坚持原始代码风格
到目前为止,我介绍了一个InstancePerTestCase属性并计划在程序集和类级别上支持它

如果我离开基地,请告诉我

我在你上次提交的一些实现细节上添加了评论。

就设计而言,我认为从IApplyToContext派生的属性是有道理的。 我想我更喜欢比 InstancePerTestCase 更通用的东西,也许

C# [FixtureLifeCycle(LifeCycle.InstancePerTestCase]

这将允许您在装配级别设置它并在单个固定装置上重置它。

CharliePoole 你太过分了,非常感谢你的反馈和帮助

我想我正在慢慢地但肯定地了解事物是如何构建的/什么是什么等等

这是我对 LifeCycle 枚举的建议(我还根据您的建议更新了我的分支)

    /// <summary>
    /// Specifies the lifecycle for a fixture.
    /// </summary>
    public enum LifeCycle
    {
        /// <summary>
        /// A single instance is created and for all test cases.
        /// </summary>
        SingleInstance,

        /// <summary>
        /// A new instance is created for each test case for fixtures that are marked with <see cref="ParallelizableAttribute"/>.
        /// </summary>
        InstancePerTestCaseForParallelFixtures,

        /// <summary>
        /// A new instance is created for each test case.
        /// </summary>
        InstancePerTestCase
    }

@jnm2您如何看待这种方法 - 首先您是该功能的“赞助商”。 我对InstancePerTestCaseForParallelFixtures有点不确定,这对于初始实现来说可能有点太多了。 像往常一样,一旦实施,我们就会坚持下去。

如果我们 __do__ 接受与并行性的联系,它必须是基于上下文的,而不是基于属性的。 也就是说,任何测试都可以并行运行,所以它更像是 `InstancePerTestCaseWhenCasesAreParallel。 平行装置并不特别需要此功能。 也许最好由用户决定。

我也同意它不应该以任何方式与并行性耦合。 并行性是添加此功能的原因,但一旦我们同意此功能有用,在我看来,它不应该反映在代码中

我同意,我理解仅在并行场景中使用它的用例,因为那是 NUnit 当前默认模型会咬你的地方,但希望这将是未来的默认生命周期(nunit 4?),因为我认为它真的应该有从一开始就这样

也许我们应该把它留在 SingleInsance/InstancePerTestCase

另一个问题是我们是否应该在这里处理 IDisposable 模式,这几乎会使 SetUp/TearDown 变得多余,但会感觉更自然的 ala XUnit 风格。

我们已经处理了任何实现 IDisposable 的测试装置。 您只需要确保它继续有效。

很好,会的。

我今天可能会开始添加测试用例,但由于它是这样一个核心机制,它可能会添加相当多的用例,以确保在 InstancePerTestCase 开启时同样的事情起作用,如果我错了,请纠正我

DisposeFixtureCommand 负责调用 Dispose。

IMO 您的主要问题是确保为每个测试__调用测试装置的整个命令流__。 到目前为止,我看到的更改正确地创建了一个似乎可以完成工作的命令流,但关键问题是如何确保为每个测试用例调用它。 (请注意,fixture 的命令结构与测试用例的命令结构不同。)我认为复合工作项必须对上下文敏感并正确调用命令(如您所做的那样),但我不确定它是否会有机会为 __every__ 测试用例这样做。 (我只是在阅读代码,所以我可能是错的。)

@CharliePoole看起来您是对的,我对执行测试方法的确切内容做了一些绘图:
image

目前我在 RunChildren 循环中创建了测试对象实例,但这对于重试/重复命令来说还不够,更不用说我是否错过了其他包装器/有人需要新的包装器。
它必须更接近 TestMethodCommand

我更新了最新的变化
https://github.com/avilv/nunit/tree/instance-per-test-2

删除InstancePerTestCaseWhenCasesAreParallel
经由在CompositeWorkItem MakeOneTimeSetUpCommand在SimpleWorkItem添加构建体/处置夹具内MakeTestCommand,以及跳过它实现FixtureLifeCycle

它似乎工作得很好,而且对框架的​​构建方式也更有意义
现在我只需要添加一堆单元测试来确保它一切正常

这有可能在下一个版本之前得到审查吗? 我当然还在改进/改变

这种方法对我来说听起来不错,如果可以的话,我很乐意在下一个版本中使用它!

我们有机会对此进行审查吗? 感觉真的很接近

想知道此功能是否有任何变动? 似乎@avilv已经非常接近了。 长期以来一直期待这样的功能,并且很高兴使用它!

谢谢大家!

这个功能现在有用吗?

@CharliePoole你能评论一下@avilv 的最新版本吗? 它似乎得到了全面实施。 我想很多人都在等待这个功能。

@janissimsons对不起,但我不再参与这个项目,所以我的评论不会向前推进。 @nunit-framework-team 不能有人审查这个吗?

感谢为合并#3427 所做的所有工作。 @rprouse知道我们什么时候可以在发布中期待这一点吗?

我们如何使用新功能? 有人可以写一篇关于新属性以及如何正确使用它们的简短描述吗?

我计划在接下来的几周内发布。 我想等到。 NET 5 是最终版本,并在发布前对其进行一些最终测试。 我怀疑会有任何问题,但它是如此接近,我认为最好等待。

@rprouse有什么估计什么时候发布新版本? 我想开始使用这个功能。

@janissimsons很快。 我们正在解决 .NET 5.0 版本的一些问题和其他一些问题,然后我会发布。 您可以在此项目板上跟踪状态, https://github.com/nunit/nunit/projects/4

这是个好消息!

我是否正确理解这将使 NUnit 中的以下内容成为可能?

结合所有这些:

  • 测试参数化
  • 并行运行测试。
  • 能够并行运行同一测试的参数化变体。
  • 测试可以是数据驱动的

过去在这里尝试了一些东西https://github.com/jawn/dotnet-parallel-parameterized-tests

新版本是否仍与 .NET Full Framework 4.7.2 或至少 4.8 兼容?

此致,
DR

@jawn我相信这一切都会奏效,是的。 我已经测试了所有这些东西,但是 NUnit 有很多特性,所以可能会有一些特性组合的边缘情况。 随意拉我们每晚的 NuGet 包并进行测试! 我们希望在发布之前进行更多测试和反馈。

@drauch是的,3.13 版本仍将支持回 .NET 3.5。

@avilv 干得好!!
顺便说一下,您使用哪种工具创建了那个漂亮的流程图?
谢谢

@rprouse您能否还更新有关如何使用此新功能的文档? 谢谢!

@janissimsons我计划更新该版本的文档。 简短的版本是将[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]到您的测试类中,可能与[Parallelizable(ParallelScope.All)]并且您的测试类的实例将为类中运行的每个测试实例化。 这样做允许您在并行测试运行中使用类中的成员变量,而不必担心其他测试会更改变量。

@LirazShay屏幕截图中的图表是在 Visual Studio Enterprise 中创建的。 例如,您可以右键单击解决方案资源管理器中的选择并选择“在代码映射上显示”。 https://docs.microsoft.com/en-us/visualstudio/modeling/map-dependencies-across-your-solutions ReSharper 具有类似的功能。

@jnm2 非常感谢!!!
我有 VS 企业版,但不知道此功能非常有用
非常感谢你的提示

我是否正确地认为这已经发布并且是 3.13 版本的一部分?

我是否正确地认为这已经发布并且是 3.13 版本的一部分?

我相信已经观察到并修复了一些问题,特别是关于 #3720 中的程序集级属性应用程序,我们现在正在等待 3.13.1..

NUnit 3.13.1 已经发布。
还有什么问题吗?

不,这就是它被关闭的原因@andrewlaser

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

UyttenhoveSimon picture UyttenhoveSimon  ·  3评论

ffMathy picture ffMathy  ·  3评论

DustinKingen picture DustinKingen  ·  4评论

jnm2 picture jnm2  ·  4评论

xplicit picture xplicit  ·  5评论