这个环境上下文如何变为空?

时间:2010-05-15 14:33:52

标签: c# .net unit-testing static abstract-class

任何人都可以帮我解释一下TimeProvider.Current在下面的课程中如何变为空吗?

public abstract class TimeProvider
{
    private static TimeProvider current =
        DefaultTimeProvider.Instance;

    public static TimeProvider Current
    {
        get { return TimeProvider.current; }
        set
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }
            TimeProvider.current = value;
        }
    }

    public abstract DateTime UtcNow { get; }

    public static void ResetToDefault()
    {
        TimeProvider.current = DefaultTimeProvider.Instance;
    }
}

观察

  • 所有直接引用TimeProvider的单元测试也会在Fixture Teardown中调用ResetToDefault()。
  • 涉及 no 多线程代码。
  • 偶尔,其中一个单元测试失败,因为TimeProvider.Current为null(抛出NullReferenceException)。
  • 这只发生在我运行整个套件时,而不是在我运行单个单元测试时,向我建议有一些微妙的测试相互依赖性。
  • 大约每五到六次测试运行一次。
  • 当发生故障时,似乎在涉及TimeProvider.Current的首次执行测试中发生。
  • 多个测试可能会失败,但在给定的测试运行中只有一个失败。

FWIW,这里也是DefaultTimeProvider类:

public class DefaultTimeProvider : TimeProvider
{
    private readonly static DefaultTimeProvider instance =
        new DefaultTimeProvider();

    private DefaultTimeProvider() { }

    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }

    public static DefaultTimeProvider Instance
    {
        get { return DefaultTimeProvider.instance; }
    }
}

我怀疑静态初始化会发生一些微妙的相互作用,在所有静态初始化完成之前,实际上允许运行时访问TimeProvider.Current,但我无法完全理解它。

感谢任何帮助。


FWIW我刚扔了

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
在getter中

,并且它始终为测试运行中的所有测试用例报告相同的ID,因此该问题似乎与线程无关。

2 个答案:

答案 0 :(得分:7)

仅基于此代码,Current可能是null,因为它被设置为null。这显然对你没有帮助。

您能提供测试代码吗?如果存在测试相互依赖性,那么对于读者来说,提供任何反馈都会有所帮助。

与此同时,Jon Skeet关于单身人士的文章可能会有所帮助,因为DefaultTimeProvider实际上是一个单身人士:http://csharpindepth.com/Articles/General/Singleton.aspx

答案 1 :(得分:6)

由于Peter Ritchie提供的链接,我可能会对此作出部分答复,但我无法完全解释正在发生的事情。似乎在TimeProvider的静态启动和DefaultTimeProvider之间存在某种竞争。它可能与beforefieldinit

有关

更改实施似乎解决了这个问题。如果没有,它肯定使竞争条件变得更加罕见,到了我还没有看到的地步。

我将TimeProvider的初始化更改为:

public abstract class TimeProvider
{
    private static TimeProvider current;

    static TimeProvider()
    {
        TimeProvider.current = new DefaultTimeProvider();
    }

    //...
}

而DefaultTimeProvider就是这样:

public class DefaultTimeProvider : TimeProvider
{
    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }
}

现在只有一个静态初始值设定器(TimeProvider),因为它是一个显式的静态构造函数,所以该类在beforefieldinit之前没有标记。

这似乎已经成功了......