【问题标题】:How can this Ambient Context become null?这个环境上下文怎么会变成空?
【发布时间】:2011-02-19 21:26:45
【问题描述】:

谁能帮我解释一下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()。
  • 没有涉及多线程代码。
  • 有时,其中一个单元测试会失败,因为 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,因此问题似乎与线程无关。

【问题讨论】:

  • 如果使用静态构造函数而不是静态字段初始化,是否也会发生这种情况?在 DefaultTimeProvider 上。
  • 我把这个放在我的博客上,因为我在查找环境上下文的细节时遇到了一些问题 - 希望你不介意:relentlessdevelopment.wordpress.com/2013/09/30/…
  • 我在 MVVM 框架 Caliburn.Micro 及其扩展库 Caliburn.Micro.Contrib 中遇到过类似的情况。使用扩展库中的这个 [原始代码] (pastebin.com/0eydNkEQ),我无法在 Visual Studio 之外运行我的 WPF 应用程序(但我可以很好地调试应用程序)。据我记得,我得到了 Stack Overflow 异常,因为 _baseTransformName 字段被分配了 FallbackNameTransform 方法,并且该方法由框架通过 ViewLocator.TransformName 调用。我不得不使用 [此解决方法] (pastebin.com/Z4hVLzUm)。

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


【解决方案1】:

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

您能提供测试的代码吗?如果存在测试相互依赖关系,这将有助于读者提供任何反馈。

与此同时,Jon Skeet 关于单例的文章可能会有所帮助,因为 DefaultTimeProvider 有效地充当单例:http://csharpindepth.com/Articles/General/Singleton.aspx

【讨论】:

  • 我无法在此处转储 113 个 xUnit.net 测试,如果存在测试相互依赖,我无法确定是哪一个导致它。
  • xUnit,你能轻松地将它们转换为 VS 单元测试并在 VS 单元测试运行器中运行它们,看看是否有区别?也许 R# 测试运行器会更容易?这会告诉你这是否是跑步者的副作用。否则,我会在其中抛出一些跟踪输出(尤其是在 DefaultTimeProvider.get_Instance 中),以查看它相对于其他操作在哪里为空。
  • 我认为你有办法,因为我刚刚转换了 from MSTest,之前没有遇到过问题。但是,xUnit.net 自己的 GUI 运行程序没有问题,因此问题可能出在 TestDriven.net 上。我会跟进 Jamie。
  • 我将此标记为答案。并不是说它完全解释了发生了什么,但提供的链接至少教会了我一些我以前不知道的静态初始化的知识(有关更多信息,请参阅我自己在此页面上的答案)。
【解决方案2】:

感谢 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。

这似乎成功了……

【讨论】:

    猜你喜欢
    • 2013-08-12
    • 2023-01-16
    • 2023-01-04
    • 2021-09-28
    • 2012-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多