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

Mar*_*ann 12 .net c# static abstract-class unit-testing

任何人都可以帮我解释如何TimeProvider.Current在下面的类中变为null?

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

意见

  • 所有直接引用TimeProvider的单元测试也会在Fixture Teardown中调用ResetToDefault().
  • 没有涉及到多线程代码.
  • 偶尔,其中一个单元测试失败,因为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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

我怀疑是有一些微妙的关系与静态初始化在运行时实际上是允许访问回事TimeProvider.Current所有静态初始化完成之前,但我不能完全把我的手指上.

任何帮助表示赞赏.


FWIW我刚刚扔了

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Run Code Online (Sandbox Code Playgroud)

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

Pet*_*hie 7

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

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

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


Mar*_*ann 6

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

改变实施似乎已经解决了这个问题.如果没有,它肯定使比赛条件变得更加罕见,到了我尚未看到它的程度.

我将TimeProvider的初始化更改为:

public abstract class TimeProvider
{
    private static TimeProvider current;

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

    //...
}
Run Code Online (Sandbox Code Playgroud)

而DefaultTimeProvider就是这样的:

public class DefaultTimeProvider : TimeProvider
{
    public override DateTime UtcNow
    {
        get { return DateTime.UtcNow; }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在只有一个静态初始化程序(TimeProvider),因为它是一个显式的静态构造函数,所以该类在beforefieldinit之前没有标记.

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