2010-05-15 11 views
12

¿Alguien me puede ayudar a explicar cómo TimeProvider.Current puede ser nulo en la siguiente clase?¿Cómo puede ser que este contexto ambiental se vuelva nulo?

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

Observaciones

  • todas las pruebas unitarias que hacen referencia directamente TimeProvider también invoca ResetToDefault() en su Fixture desmontaje.
  • Hay no código multiproceso involucrado.
  • De vez en cuando, una de las pruebas de unidad falla porque TimeProvider.Current es nulo (se lanza NullReferenceException).
  • Esto solo ocurre cuando ejecuto todo el paquete, pero no cuando solo realizo una prueba unitaria, lo que me sugiere que hay una sutil interdependencia de prueba.
  • Sucede aproximadamente una vez cada cinco o seis ejecuciones de prueba.
  • Cuando ocurre una falla, parece estar ocurriendo en las primeras pruebas ejecutadas que involucran TimeProvider.Current.
  • Más de una prueba puede fallar, pero solo una falla en una ejecución de prueba determinada.

Fwiw, aquí está la clase DefaultTimeProvider así:

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

sospecho que hay algo sutil interacción pasando con la inicialización estática, donde la duración es de hecho permitió acceder a la TimeProvider.Current antes de toda la inicialización estática ha terminado, pero no puedo entenderlo.

Cualquier ayuda es apreciada.


Fwiw Me acaba de lanzar

Console.WriteLine(Thread.CurrentThread.ManagedThreadId); 

en el captador, y constantemente informa el mismo ID para todos los casos de prueba en una prueba de funcionamiento, por lo que el problema no parece estar relacionada con el roscado.

+0

qué ocurre también si se utiliza un constructor estático en lugar de inicialización campo estático? En el DefaultTimeProvider que es. – asgerhallas

+0

Sí, lo hace ... –

+0

Puse esto en mi blog, ya que estaba teniendo algunos problemas para encontrar detalles del contexto ambiental - espero que no te importe: http://relentlessdevelopment.wordpress.com/2013/09/30/testing-datetimes-and-other-things-with-an-ambient-context/ –

Respuesta

7

Basado únicamente en este código, Current podría ser null en función de que se establezca en null. Obviamente, esto no es útil para ti.

¿Podría proporcionar el código para las pruebas? Si hay una interdependencia de prueba, sería útil para los lectores a fin de proporcionar cualquier comentario.

Por el momento, posiblemente, el artículo de Jon Skeet en embarazos únicos podría ser útil, ya que DefaultTimeProvider está actuando efectivamente como un producto único: http://csharpindepth.com/Articles/General/Singleton.aspx

+0

No puedo descargar 113 pruebas de xUnit.net aquí, y si hay una interdependencia de prueba, no puedo determinar cuál es la causa. –

+1

xUnit, ¿puede convertirlos fácilmente en pruebas de la unidad VS y ejecutarlos en el corredor de prueba de la unidad VS para ver si hay alguna diferencia? Tal vez el corredor de la prueba R # sería más fácil? Eso te diría si es un efecto secundario del corredor. De lo contrario, arrojaría algún resultado de rastreo allí (especialmente en DefaultTimeProvider.get_Instance) para ver dónde es nulo en relación con otras operaciones. –

+0

Creo que estás tramando algo, porque acabo de convertir * de * MSTest, y no tuve el problema antes. Sin embargo, no hay ningún problema con el propio corredor GUI de xUnit.net, por lo que el problema puede ser con TestDriven.net. Voy a seguir con Jamie. –

6

que pueden tener una respuesta parcial a esto, gracias a los enlaces proporcionados por Peter Ritchie , aunque no puedo explicar completamente lo que está pasando. Parece que hubo algún tipo de carrera entre la inicialización estática de TimeProvider y DefaultTimeProvider. Puede tener que ver con beforefieldinit.

Al cambiar la implementación parece que se ha resuelto el problema. Si no, ciertamente ha hecho que la condición de la carrera sea mucho más rara, hasta el punto en que aún no la he visto.

me cambió la inicialización de TimeProvider a esto:

public abstract class TimeProvider 
{ 
    private static TimeProvider current; 

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

    //... 
} 

Y DefaultTimeProvider simplemente para esto:

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

Ahora hay sólo un inicializador estático en juego (TimeProvider) y ya que es una estática explícita constructor la clase no está marcada antes del campoinicio.

Esto parece haber hecho el truco ...

Cuestiones relacionadas