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

时间:2018-12-20 13:49:42

标签: unit-testing xunit fixtures xunit.net

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

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);
    }
}

两个类均未标记任何集合属性。它们都属于同一程序集。

我看到这些测试失败(但只是偶尔发生),这只能由XUnit在测试类之间共享FixtureData实例,并且首先运行TestForNull来解释(因为它有副作用)。

但是,XUnit documentation清楚地表明,类固定装置是“在单个类中的测试之间共享对象实例”。

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

我正在将xUnit用于.NET Core 2.3.1。

2 个答案:

答案 0 :(得分:2)

否,您已经正确阅读了文档。 TL; DR,因为测试类可以并行运行,所以类夹具必须是独立的,这就是它们的全部重点。

如果xUnit中有一个错误,这会令我感到惊讶,因为此功能/功能稳定且未进行更改。

如果您可以使实际样本失败,那么,是的,这是xUnit中的一个错误,但我是说a)现在没有失败b)您不会使其失败c)SELECT未损坏;)

希望这会有所帮助;)

答案 1 :(得分:2)

正如鲁本·巴特林克(Ruben Bartelink)在答复中说的那样,“选择没有失败”,这意味着XUnit是一个行之有效的测试框架,这是一项非常核心的功能,而且问题很少在他们身边。

此外,深入研究XUnit代码,这就是生成类固定装置的作用:({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的调用中可以很容易地看出,对于测试用例,每次都会重新生成类夹具

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

在我的示例中,我不小心简化了更多内容。我发现这可能是发生情况的更好示例:

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实例,单例实际上也会使它使用相同的实例。

在我的情况下,我正在独立查看测试类,但我没有意识到有一个单例,因此我认为问题与测试装置有关(错误的假设)。而且由于我在问问题时遗漏了部分内容,因此某人不可能正确地了解出了什么问题。

故事的寓意

  • 信任经过测试的框架(尤其是专门从事测试的框架!)
  • Stop overusing singletons
  • 在向StackOverflow提问之前,请确保已掌握问题的所有相关部分