XUnit 是否在测试 类 之间共享夹具实例?

Does XUnit share fixture instances across test classes?

这是我的一些代码的简化版本:

public class FixtureData
{
    public object SomeValue { get; set; }
}

public class TestForNull : IClassFixture<FixtureData>
{
    private readonly FixtureData _data;

    public TestForNull(FixtureData data)
    {
        _data = data;
    }

    [Fact]
    public void TestForNull()
    {
        _data.SomeValue = null;
        Assert.Null(_data.SomeValue);
    }
}

public class TestForObject : IClassFixture<FixtureData>
{
    private readonly FixtureData _data;

    public TestForObject(FixtureData data)
    {
        _data = data;
    }

    [Fact]
    public void TestForObject()
    {
        Assert.NotNull(_data.SomeValue);
    }
}

两个class都没有标记任何集合属性。他们都属于同一个程序集。

我发现这些测试失败了(但只是偶尔失败),这只能通过 XUnit 在测试 classes 和 TestForNull [=31 中共享 FixtureData 实例来解释=] 首先(因为它有副作用)。

但是,XUnit documentation 清楚地表明 class 固定装置是 "shared object instance across tests in a single class"。

这是一个错误吗?我应该改变我使用灯具的方式吗?

我正在为 .NET Core 2.3.1 使用 xUnit。

不,您已正确阅读文档。 TL;DR 因为测试 Classes 可以 运行 并行,Class 装置 是独立的,这就是它们的全部意义。

如果 xUnit 中存在错误,我会感到惊讶,因为这个 feature/facility 是稳定的并且没有发生变化。

如果你能让你的实际样本失败,那么,是的,这是 xUnit 中的一个错误,但我是说 a) 它现在没有失败 b) 你不会让它失败 c) SELECT 没坏 ;)

希望对您有所帮助 ;)

正如 Ruben Bartelink 在他的回复中所说,"SELECT is not broken",这意味着这是 XUnit 的一个非常核心的功能,一个经过充分验证的测试框架,而且问题出在他们这边的可能性很小。

此外,深入研究 XUnit 代码,这就是它生成 class 固定装置的作用:(Source)

var createClassFixtureAsyncTasks = new List<Task>();
foreach (var interfaceType in testClassTypeInfo.ImplementedInterfaces.Where(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IClassFixture<>)))
    createClassFixtureAsyncTasks.Add(CreateClassFixtureAsync(interfaceType.GetTypeInfo().GenericTypeArguments.Single()));

if (TestClass.TestCollection.CollectionDefinition != null)
{
    var declarationType = ((IReflectionTypeInfo)TestClass.TestCollection.CollectionDefinition).Type;
    foreach (var interfaceType in declarationType.GetTypeInfo().ImplementedInterfaces.Where(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IClassFixture<>)))
        createClassFixtureAsyncTasks.Add(CreateClassFixtureAsync(interfaceType.GetTypeInfo().GenericTypeArguments.Single()));
}

await Task.WhenAll(createClassFixtureAsyncTasks);

从对 CreateClassFixtureAsync 的调用中可以很容易看出 class 固定装置每次 都会为测试用例重新生成。

那为什么我观察到这种行为?

我在示例中不小心简化了更多我应该拥有的内容。我发现这可能是正在发生的事情的一个更好的例子:

public class FixtureData
{
    public object SomeValue => HiddenSingleton.Instance.SomeValue;
}

public class HiddenSingleton
{
    private static HiddenSingleton _instance;
    public static HiddenSingleton Instance
    {
        get
        {
            if (_instance != null) return _instance;

            _instance = new HiddenSingleton();
            return _instance;
        }
    }

    public object SomeValue { get; set; }
}

public class TestForNull : IClassFixture<FixtureData>
{
    private readonly FixtureData _data;

    public TestForNull(FixtureData data)
    {
        _data = data;
    }

    [Fact]
    public void TestForNull()
    {
        _data.SomeValue = null;
        Assert.Null(_data.SomeValue);
    }
}

public class TestForObject : IClassFixture<FixtureData>
{
    private readonly FixtureData _data;

    public TestForObject(FixtureData data)
    {
        _data = data;
    }

    [Fact]
    public void TestForObject()
    {
        Assert.NotNull(_data.SomeValue);
    }
}

在这种情况下,直接看,很明显:即使XUnit为每个测试生成一个独立的FixtureData实例,单例实际上使它们使用相同的实例。

在我的例子中,我正在独立地查看测试 classes 并且我没有意识到有一个单例,所以我假设问题与测试装置有关(不正确的假设)。而且由于我在提问时遗漏了部分内容,所以有人不可能正确地找出问题所在。

故事寓意:

  • 信任经过测试的框架(特别是如果他们专注于测试!)
  • Stop overusing singletons
  • 在 Whosebug 中提问之前,请确保您已了解问题的所有相关部分