Autofixture: NCrunch 和 AutoFixture 集成问题

创建于 2017-08-23  ·  15评论  ·  资料来源: AutoFixture/AutoFixture

目前 NCrunch 不支持 AutoFixture,这在他们的文档中明确说明。 我向 xUnit 提出了一个问题,并试图以某种方式解决它,但它似乎是更复杂的问题。

我将在这里邀请 NCrunch 开发人员一起讨论这个问题。 也许,我们还可以将其进一步移至 xUnit 表面。

鉴于我们都是非常受欢迎的产品,我们应该努力找到一种与 NCrunch 合作的方法😅

question

最有用的评论

@remcomulder @zvirja只是想说这个讨论(它帮助我理解了一个类似的问题),以及你在这里展示的奉献精神,

所有15条评论

你好,我是 NCrunch 的开发者。

我很乐意与您合作以找到解决此问题的方法。 使用当前的设计,我很遗憾没有选择仅在我这边的集成点解决问题。 我相信我的跑步者可能不是唯一遇到当前设计问题的跑步者。

您可能已经通过我撰写的各种文档和支持论坛帖子了解到,问题的根源在于 AutoFixture 随机生成测试参数。 由于测试参数是识别测试并随后执行测试的关键元素,如果每次构建/发现测试时参数都发生变化,则无法选择性地执行测试。

我能想到的解决这个问题的唯一可靠方法是删除所有随机生成的测试参数,而是使用固定值(即占位符或常量),或者以其他方式将参数值的播种限制为测试用例本身。 这样,测试将始终完全相同,并且可以始终如一地发现它们与任何其他测试相同。 我接触过的每个使用 AutoFixture 进行参数生成的用户都对他们为了测试目的不“关心”的参数这样做了,所以我希望这种方法在用户眼中不会有任何真正的缺点. 这样做的一个好处是它也可以立即与所有版本的 NCrunch 一起使用,并且不需要对 NCrunch 或任何其他运行程序进行任何代码更改。

@remcomulder 非常感谢您在这里参与 - 非常感谢! 我对此进行了一些调查,并想分享我的发现。 我的所有发现仅适用于xUnit

TL DR: xUnit 支持这样的理论,NCrunch 也应该为 xUnit 支持它们。 对于 NUnit - 这是一个悬而未决的问题,我还没有调查过。


我们使用的功能似乎对 xUnit 是合法的。 我们有自己的TestDataDiscoverer ,它表明我们的理论不能被预先发现(因为我们生成随机数据)。 稍后我们用这个发现器装饰我们的AutoDataAttribute 。 xUnit尊重此属性,并且在发现期间不解析参数值。

我相信我的跑步者可能不是唯一遇到当前设计问题的跑步者。

实际上,事实并非如此,R# 和 VS 都适用于此类理论。 它们还允许重新运行特定理论,即使它包含自动生成的数据。 我建议关注 VS,因为它也包含发现和运行阶段并且是开源的。

考虑以下测试代码:

using Ploeh.AutoFixture.Xunit2;
using Xunit;

namespace Playground
{
    public class UnitTest
    {
        [Fact]
        public void StableTest()
        {
            Assert.True(true);
        }

        [Theory]
        [InlineData(1)]
        public void StableInlineTest(int value)
        {
            Assert.Equal(1, value);
        }

        [Theory, AutoData]
        public void VolatileTest(int value)
        {
            Assert.True(value % 2 == 0);
        }

        [Theory]
        [InlineAutoData(10)]
        [InlineAutoData(20)]
        [InlineAutoData(30)]
        public void VolatileTestWithInline(int value, int autoValue)
        {
            Assert.NotEqual(value, 40);
        }
    }
}

.NET Framework 测试库项目。 VS 2017.3。 目标框架:4.5.2。 安装的软件包:

  • xunit 2.2.0
  • xunit.runner.visualstudio 2.2.0
  • AutoFixture 3.50.6
  • AutoFixture.Xunit2 3.50.6

如果您在 VS 中触发发现,您将看到以下输出:
image

您可能会注意到,对于支持数据发现的理论 ( StableInlineTest ),VS runner 显示测试将使用的实际数据 ( 1 )。 对于不支持数据发现并包含自动生成数据( VolatileTestVolatileTestWithInline )的测试,VS 不会发现理论案例,而只会向您展示整个理论。 只有在运行后,您才能看到此特定调用的值:
image

现在您可以重新运行特定理论,它将再次运行_所有理论案例_。

如您所见,实际上有一种方法可以支持此类理论,而 xUnit 完美地做到了这一点。 NCrunch 应该考虑到一些理论案例无法预先发现的事实。 对于此类理论,您需要重新运行整个理论,而不是特定案例。 我不明白为什么这是不可能的。

我能想到的解决这个问题的唯一可靠方法是删除所有随机生成的测试参数,而是使用固定值(即占位符或常量)代替

目前 xUnit 没有公开 API 来更改显示名称和用占位符替换生成的数据。 我已经为他们创建了一个问题(请参阅此处),但是 Brad 的答复是它不真实,他们建议简单地禁用我们已经在做的发现。

或以其他方式将参数值的播种限制为测试用例本身。

不幸的是,这对我们的产品来说目前是不可能的,而且应该重写很多东西以支持单一种子。


会员资料

此处的文档中

public class MemberDataSample
{
    public IEnumerable<object[]> GetData()
    {
        yield return new object[]
        {
            DateTime.Now
        };
    }

    [Theory, MemberData(nameof(GetData), DisableDiscoveryEnumeration = true)]
    public void DateTheory(DateTime dt)
    {
        Assert.True(DateTime.Now - dt < TimeSpan.FromMinutes(1));
    }
}

xUnit 绝对合法,因为有DisableDiscoveryEnumeration属性。 它的工作原理类似于上面的示例 - 没有预先发现理论案例。


底线

似乎 xUnit 提供了工具来了解测试是否易失(通过预发现枚举支持)。 您可以使用 VS 实现作为示例来了解它们如何处理并在您的产品中执行相同的操作。 很可能,他们只是使用 xUnit SDK 和他们的消息接收器。

鉴于 R# 和 VS 都支持这样的理论,这让我觉得我们的产品实际上一切都很好。

至于 NUnit - 让我们稍后再讨论,因为我还没有调查过。 可能,我们没有这样的 API。

鉴于我的发现,您能否分享您对 xUnit 支持的看法? 你会添加对
xUnit(停止重新运行所有测试)并停止显示不兼容警告? 😉

看来我应该在这里向你道歉。 由于您解释的原因,您在上面描述的用例在 NCrunch 下可以正常工作。 Xunit 避免预先列举理论,并将其分解为一个单独的测试,在那里可以安全地识别和执行。 现在对此进行测试,我可以确认 NCrunch 正确执行此操作。 似乎我们对 InlineAutoData 没有明显的问题。

我不知道为什么这对我来说早些时候失败了。 我目前无法自己创建一个失败的场景。 我知道我已经有用户向我提到 InlineAutoData 不起作用,但我想他们需要站出来并提供这种情况的示例。

我想提请您注意一个特定的用例,我知道它会破坏 NCrunch 和 VS Runner。 我认为它也会破坏 ReSharper 和 TD.NET,但我还没有测试这些,因为我没有安装它们:

using Ploeh.AutoFixture;
using Ploeh.AutoFixture.Xunit2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace XUnitAutoFixture
{
    public class TestFixture
    {
        private static readonly Fixture Fixture = new Fixture();

        public static IEnumerable<object[]> SomeTestData()
        {
            yield return new object[] { Fixture.Create<string>() };
            yield return new object[] { Fixture.Create<string>() };
        }

        [Theory, MemberData(nameof(SomeTestData))]
        public void Test(object value)
        {

        }
    }
}

以上代码在所有选择性执行场景下都会失败。 但是,如果在同一步骤中发现并执行测试,它将通过。 这种情况是 NCrunch 发出关于 AutoFixture 的警告背后的催化剂,因为用户正在这样做并寻求支持。 鉴于我不知道 AutoFixture 禁用预枚举,我曾(错误地)假设 InlineAutoData 是相同的。

我目前的假设是您不支持这种情况。 这样对吗?

现在对此进行测试,我可以确认 NCrunch 正确执行此操作。

那太棒了! 很高兴听到 NCrunch 实际上完全支持 xUnit 🎉 至于 NUnit - 它一直很笨拙,我们需要调查我们可以在那里做什么。

但是,我仍然观察到 NCrunch 和 AutoFixture 的问题。 目前我有带有 AutoFixture 的 xUnit 项目,如果我更改一行,所有测试都会重新运行。 当您检测到 AutoFixture 以确保不会遗漏任何内容时,您似乎启用了此类行为。

是这样吗? 如果是,您能否修复它以禁用 xUnit 的这种行为,因为那里一切正常?


我想提请您注意一个特定的用例,我知道它会破坏 NCrunch 和 VS Runner。
我目前的假设是您不支持这种情况。 这样对吗?

是的,这种情况会让所有跑步者都崩溃。 但是,正如在您的论坛某处正确指出的那样,这不是因为 AutoFixture,因为您可能会编写这样的内容,但也不会起作用:

public class TestFixture
{
    public static IEnumerable<object[]> SomeTestData()
    {
        yield return new object[] { DateTime.Now.Ticks };
        yield return new object[] { DateTime.Now.Ticks };
    }

    [Theory, MemberData(nameof(SomeTestData))]
    public void Test(object value)
    {

    }
}

对于这种情况,您有责任手动禁用预发现,使其看起来像这样(注意DisableDiscoveryEnumeration属性):

public class TestFixture
{
    private static readonly Fixture Fixture = new Fixture();

    public static IEnumerable<object[]> SomeTestData()
    {
        yield return new object[] { Fixture.Create<string>() };
        yield return new object[] { Fixture.Create<string>() };
    }

    [Theory, MemberData(nameof(SomeTestData), DisableDiscoveryEnumeration = true)]
    public void Test(object value)
    {

    }
}

实际上,这应该是非常罕见的情况,因为我们有AutoDataInlineAutoData用于数据生成的属性。 此外,AutoFixture 只是一个工具,正确使用它是开发人员的责任。

我不会仅仅因为有些人可能会错误地使用工具而将 NCrunch 移​​动到特殊模式。 我们在这里能做的最好的事情是在某处放置一个已知问题来描述这个特定场景,并要求人们正确使用 xUnit(因为他们可能不知道这一点)。


如果您检测到 AutoFixture,您能否确认您以特殊方式运行测试,如果是,您能否为 xUnit 禁用该模式(对于 NUnit,最好保持原样)。 也可能这个页面也应该更新。

目前,NCrunch 在兼容性警告之外没有对 AutoFixture 进行特殊处理。 您遇到的行为可能是由于您选择的引擎模式。 它是完全可配置的 - 如果您切换到“自动运行受影响的测试,手动运行其他测试”模式,我想您会看到引擎以您期望的方式运行。

根据我从您那里学到的知识,我已经准备好从 NCrunch 中完全删除 AutoFixture 警告。 我已经被用户说服改写它,因为很早就清楚警告过于广泛并且 AutoFixture 的某些功能仍然正常工作。 我想我已经严重误解了 AutoFixture 在 xunit 下是如何实现的。

所以我想这对我们来说可能是最好的情况。 无论如何,最严重破坏我的用例在技术上不受支持,其他一切正常。 同时,如果你能找到理由原谅我过度热心的兼容性警告,我会很乐意尝试忘记我自己在测试和验证这个结论不正确时的尴尬。

同时,如果你能找到理由原谅我过度热心的兼容性警告,我会很乐意尝试忘记我自己在测试和验证这个结论不正确时的尴尬。

不用担心! 绝对没问题,我们都在这里帮助彼此了解事情😅太好了,你跟进了票,我们讨论了这个 - 太多了🥇

根据我从您那里学到的知识,我已经准备好从 NCrunch 中完全删除 AutoFixture 警告。

好吧,我们仍然有 NUnit 的问题(尽管我们正在路上),所以这个消息看起来与 NUnit 项目相关。 也许,仅对 xUnit 禁用它是有意义的,除非我们引入对 NUnit 的完全支持(或至少一种启用该支持的方法)。

我还有一件事不清楚。 您不支持例如 AutoFixture 和 NUnit 是什么意思? 是的,每次的测试名称都不同,但是是否重新运行所有测试(如果引擎模式如此设置)是否重要? 或者这是否意味着您不再为他们支持“仅受影响”引擎? 我的想法是,与其说No support ,不如缩小到一些我们不支持的特定场景,而其他场景应该没问题。

“自动运行受影响的测试,其他手动运行”

我无法在3.10.0.20安装中找到此设置。 你的意思是应该设置为trueOnly consider tests 'Out of date' if they are 'Impacted'设置? 抱歉,如果我错过了某个地方 - 我对这个产品有点陌生..


文档更新

不使用 xUnit 和 AutoFixture 从这个页面 _remove_ case 可能是有意义的,而是描述如何正确使用它(使用DisableDiscoveryEnumeration属性)。 此外,描述 xUnit 的“名称不一致的 NUnit 测试用例”示例并要求将DisableDiscoveryEnumeration属性与MemberDataAttribute一起使用也很酷。

我还有一件事不清楚。 您不支持例如 AutoFixture 和 NUnit 是什么意思? 是的,每次的测试名称都不同,但是是否重新运行所有测试(如果引擎模式如此设置)是否重要? 或者这是否意味着您不再为他们支持“仅受影响”引擎? 我的想法是,与其说不支持,不如缩小到一些我们不支持的特定场景,而其他场景应该没问题。

这是因为在 NCrunch 下测试的生命周期。 NCrunch 具有分配给每个测试的重要状态(想想通过/失败结果、代码覆盖率数据、性能数据、跟踪输出等)。 只要测试框架继续“发现”测试,即使在 VS 会话之间,该数据也会持续存在。 当测试框架报告没有具有相同标识符的测试时,则认为测试已经消失并且所有状态都被破坏。

当使用不稳定参数创建测试时,每次调用测试框架以发现测试都会导致创建一个全新的测试,因为测试的标识符已更改。 结果是每次 NCrunch 调用 NUnit 来发现测试时(在每次构建测试项目后始终如此),为具有不稳定参数的测试保持的所有状态都将被丢弃。 所以这很糟糕。 这意味着碰撞检测将不起作用,并且引擎会进行大量额外的工作,重新运行测试并快速浏览瞬态结果。

不过问题更深了。 如果测试状态的转储是唯一真正的问题,那么处理不稳定的测试仍然可以“工作”,因为它们仍然会由引擎运行并报告结果。 更深层次的问题来自 NCrunch 的并行化、选择性执行和分布式处理。

为了执行并行执行,NCrunch 需要使用多个并行执行测试的测试进程。 NUnit 的机制是测试必须在执行之前被发现。 这意味着必须在每个用于执行的进程内部执行一个完整的独立发现步骤,因此如果我们有两个进程,那么我们需要发现测试两次。 如果测试有不稳定的参数,每个进程都会有一组完全不同的测试,这使得引擎不可能在进程之间拆分完整的主测试列表来执行。 当使用分布式处理时,这个问题也被扩展了,因为远程执行进程在完全不同的环境中运行在不同的硬件上。

还有选择性执行的问题。 NCrunch 的默认操作模式是在被明确告知运行测试时始终创建新的测试进程。 这是为了清除石板并尽可能与其他跑步者保持一致。 这样的特性不能与不稳定的参数一起工作,因为产生一个新的测试过程需要重新发现测试,如果它们的参数发生变化,则随后无法识别这些测试。

NUnit 确实有自己的内部 ID 系统,可用于识别进程之间参数不稳定的测试,但该 ID 系统基于测试生成顺序(即它是增量的)。 这样的系统不能被任何需要跨多个版本的测试集管理测试状态的测试运行器使用,因为如果用户创建一个新的测试,所有的 ID 将失去顺序并且数据将变得危险地误导. NUnit 开发人员表示有兴趣从这种基于序列的系统转向从测试属性本身生成的 ID,这与 Xunit 的做法类似(并且可能不适用于不稳定的参数)。

我仍然相信解决这些问题的最好方法是对不稳定参数进行一致的播种。 总是有一个概念,即测试应该是可重复和一致的,如果测试完全随机化所有输入,则很难实现。 在实际实践中,为执行生成随机种子数据的测试在每次生成时都是一个全新的测试,因为代码的行为会根据它提供的数据而有所不同。

我无法在 3.10.0.20 安装中找到此设置。 您的意思是仅考虑测试“过时”,如果它们是“受影响”设置,应该设置为 true? 抱歉,如果我错过了某个地方 - 我对这个产品有点陌生..

转到 NCrunch 菜单,选择“设置引擎模式”,您应该会看到那里的选项。 如果它不存在,则可能您正在使用由较旧版本的 NCrunch 执行的解决方案,并且它仅显示旧引擎模式。 只需在某处创建一个新的解决方案就可以解决这个问题。

@remcomulder哇! 谢谢这么详细的解释! 现在我看到确实更容易说 AutoFixture 和 NUnit 目前不受支持,因为幕后存在一些巨大的问题😅

我仍然相信解决这些问题的最好方法是对不稳定参数进行一致的播种。

实际上,在这个PR 中,我们有一些不同的想法。 我们想更改测试名称以使其稳定。 例如,给定这样的测试:

    [Test, StableNameAutoData]
    public void Sample(int a, Data d1, DataWithToString d2, ISomeData d3)
    {
        Assert.IsTrue(true);
    }

测试名称将是:

NUnit3RunnerTest709.TestNameTester.Sample(auto<System.Int32>,auto<NUnit3RunnerTest709.Data>,auto<NUnit3RunnerTest709.DataWithToString>,auto<Castle.Proxies.ISomeDataProxy>)

在每次发现/执行期间名称将始终相同,而实际参数值每次都会不同。

鉴于您对 NUnit 的深入了解,您如何评价这种方法? 它对你有用吗? 我希望您使用理论名称,而不是绑定到特定的参数值。 因此,如果测试名称稳定,您应该不会遇到任何麻烦,因为它现在可以识别。 在我们开始实施之前,您能否确认一下? ☺️

总是有一个概念,即测试应该是可重复和一致的,如果测试完全随机化所有输入,则很难实现。

可能有一天我们会支持一个稳定的种子,以便可以用相同的参数重播测试,但是目前我们离那个还很远,所以这是不现实的😕相反,我们希望用户的断言足够精确,以便以后理解为什么即使输入在某些范围内随机化,测试也会失败。

鉴于您对 NUnit 的深入了解,您如何评价这种方法? 它对你有用吗? 我希望您使用理论名称,而不是绑定到特定的参数值。 因此,如果测试名称稳定,您应该不会遇到任何麻烦,因为它现在可以识别。 在我们开始实施之前,您能否确认一下? ☺️

不幸的是,我对 NUnit 的了解还远远不够 :( NCrunch 基本上只是采用 NUnit 返回的名称,并使用它来生成标识符。因此,理论上,只要您的解决方案确实将测试的物理名称更改/稳定为由 NUnit API 返回,那么我们这里应该没问题
至少对于 NCrunch。

需要注意的是,用户可能会使用相同的签名创建多个测试。 如果名称中的参数被剥离为原始类型,则这变得更有可能/可能。 只要您了解这些情况,您就可能会编写错误代码或阻止它发生的事情。

@remcomulder只是为了更新你,所以我们最终可以关闭这个问题。

正如上面所讨论的,xUnit 框架是原生支持的。 但是,对于 NUnit,这个问题并不清楚,因为它不支持具有可变名称的测试。

我们最近合并了一个 PR 并发布了新版本的 AutoFixture ( v3.51.0 ),它增加了对 NUnit 稳定名称的支持。 目前,需要用户方的手动操作(请参阅此处),但是在 v4 中,我将使其开箱即用。

我刚刚测试并发现,如果我使用上述方法,NCrunch 只能运行修改后的测试,而且现在它似乎可以正常工作。 我不确定是否需要你方采取某些行动(例如,将其记录在某处)。 如果您测试该新功能并让我们知道它现在是否正常工作也可以,这样我们就可以休息一下😄

@zvirja感谢您让我参与其中。 我已经对这项工作进行了快速测试,对我来说它看起来很可靠。 通过从测试名称中删除随机元素,运行程序可以一致地识别跨会话的测试,并且一切似乎都很好:)

我确实有一个想法,但我认为这可能会为我们节省一些用户支持时间。 我们都发现软件的一个问题是许多人在拿起工具之前没有阅读文档。 知道我们现在能够就如何解决 NUnit 下的唯一名称问题向他们提供建议是很有用的,但最好的方法总是让问题自行解决。

我注意到默认情况下,AutoFixture 将使用 VolatileNameTestMethodBuilder。 我接受这是出于向后兼容性的原因,我同意这是明智的。 但是如果我们允许使用环境变量覆盖这个默认值呢? 如果我们可以指定一个环境变量(例如,'AutoFixture.FixedTestNames'=='1')来强制 AutoFixture 使用 VolatileNameTestMethodBuilder,那么运行程序就可以预先指定构建器,而无需用户执行任何操作。 这对于使用多个 runner 的人来说也是一个奖励,因为他们可以为每个 runner 隐式地使用不同的构建器(无需任何努力)并获得两全其美。

你怎么认为?

我已经对这项工作进行了快速测试,对我来说它看起来很可靠。

棒极了! 🎉 这意味着我们可以关闭这个问题,因为现在一切正常。 感谢您在这里的测试和参与🥇

你怎么认为?

我想我最终得到了一个更好、更简单的解决方案 - 我们将FixedNameTestMethodBuilder作为 v4(我们的下一个主要版本)中的默认策略,该策略将在未来一两个月发布。 适当的 PR已经被批准,所以代码将在那里。 因此,我们将开箱即用,如果有人需要易变的测试名称 - 他将手动选择加入,清楚地了解后果。

稍后我们可以执行您建议的操作 - 添加一个检查环境变量/AppConfig 的切换策略,但是我更喜欢仅在确实需要时才这样做。

我想我最终得到了一个更好、更简单的解决方案 - 我们将在 v4(我们的下一个主要版本)中将 FixedNameTestMethodBuilder 作为默认策略,该策略将在未来一两个月发布。

这对我很有用:) 我很高兴! 感谢您的所有努力。

酷😉 再次感谢您的回复和合作👍

我正在关闭这个,因为双方都不需要采取更多行动。

@remcomulder @zvirja只是想说这个讨论(它帮助我理解了一个类似的问题),以及你在这里展示的奉献精神,

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