2012-03-30 21 views
8

La solución normal es esconderlo detrás de la interfaz.Cómo simular DateTime.Now en pruebas unitarias?

public class RecordService 
{ 
    private readonly ISystemTime systemTime; 

    public RecordService(ISystemTime systemTime) 
    { 
     this.systemTime = systemTime; 
    } 

    public void RouteRecord(Record record) 
    { 
     if (record.Created < 
      systemTime.CurrentTime().AddMonths(-2)) 
     { 
      // process old record 
     } 

     // process the record 
    } 
} 

En la prueba unitaria puede utilizar objeto de burla y decidir qué volver

[TestClass] 
public class When_old_record_is_processed 
{ 
    [TestMethod] 
    public void Then_it_is_moved_into_old_records_folder() 
    { 
     var systemTime = A.Fake<ISystemTime>(); 
     A.CallTo(() => system.Time.CurrentTime()) 
      .Returns(DateTime.Now.AddYears(-1)); 

     var record = new Record(DateTime.Now); 
     var service = new RecordService(systemTime); 

     service.RouteRecord(record); 

     // Asserts... 
    } 
} 

no me gusta para inyectar otra interfaz en mi clase sólo para obtener la hora actual. Se siente una solución demasiado pesada para un problema tan pequeño. La solución es usar clase estática con función pública.

public static class SystemTime 
{ 
    public static Func<DateTime> Now =() => DateTime.Now; 
} 

Ahora podemos eliminar la inyección ISystemTime y RecordService se parece a esto

public class RecordService 
{ 
    public void RouteRecord(Record record) 
    { 
     if (record.Created < SystemTime.Now.AddMonths(-2)) 
     { 
      // process old record 
     } 

    // process the record 
    } 
} 

En las pruebas unitarias que pueden ya desafiar a la hora del sistema con la misma facilidad.

[TestClass] 
public class When_old_record_is_processed 
{ 
    [TestMethod] 
    public void Then_it_is_moved_into_old_records_folder() 
    { 
     SystemTime.Now =() => DateTime.Now.AddYears(-1); 
     var record = new Record(DateTime.Now); 
     var service = new RecordService(); 

     service.RouteRecord(record); 

     // Asserts... 
    } 
} 

Por supuesto, hay una desventaja en todo esto. Estás usando campos públicos (¡The HORROR!) Para que nadie te detenga escribiendo código como este.

public class RecordService 
{ 
    public void RouteRecord(Record record) 
    { 
     SystemTime.Now =() => DateTime.Now.AddYears(10); 
    } 
} 

También creo que es mejor educar a los desarrolladores que crear abstracciones solo para protegerlos de cometer errores. Otros posibles problemas están relacionados con la ejecución de las pruebas. Si olvida restablecer la función a su estado original, podría afectar otras pruebas. Esto depende de la forma en que el corredor de prueba de la unidad ejecuta las pruebas. Puede utilizar la misma lógica para burlarse de las operaciones del sistema de archivos

public static class FileSystem 
{ 
    public static Action<string, string> MoveFile = File.Move; 
} 

En mi opinión la implementación de este tipo de funcionalidad (burlarse de tiempo, las operaciones del sistema de archivos simples) usando las funciones públicas es perfectamente aceptable. Hace que el código sea más fácil de leer, disminuye las dependencias y es fácil burlarse de las pruebas unitarias.

+1

¿Has utilizado moq? (o cualquier otro marco de burla) http://code.google.com/p/moq/ – Magrangs

+0

He solucionado el formateo de tu código. Por favor, compárelo con su versión para aprender a hacerlo correctamente. –

+1

@Magrangs: como todos los otros marcos de burla "normales", Moq no puede simular métodos y propiedades estáticos como 'DateTime.Now'. –

Respuesta

3

No necesita implementar esto a mano. Puede usar el marco de Moles para hacer esto. channel 9

+2

Y moq .. y muchos otros marcos burlones :-) – Magrangs

+0

De hecho, esta pregunta me sorprendió como el primer ejemplo de moles en el video es lo fácil que es burlarse DateTime.Now :) – daryal

+1

@Magrangs: No es correcto. Vea mi respuesta a su otro comentario anterior. –

3

No diría que obtener la hora actual fue una tarea tan pequeña, ¿qué pasa si su aplicación se vuelve internacional y se usa en múltiples zonas horarias, en qué zona horaria obtiene la hora actual, lo más probable es que desee una zona horaria común para usar, sin importar su ubicación?

La interfaz le permite abstraer este conocimiento; Consideraría recuperar el tiempo "actual" como una tarea bastante complicada.

0

Teniendo en cuenta que estamos hablando de idiomatic C#, no entiendo muy bien los problemas con la ceremonia detrás de la interfaz. Básicamente, tiene un TimeProvider que inyecta en el constructor como una dependencia y proporciona un stub para que controle la lógica del código relevante bajo prueba.

No creo que sus otros enfoques brinden beneficios y tienen el aspecto negativo de no ser idiomáticos, así como la eliminación de los aspectos útiles de la inyección de dependencia (documentación de dependencias, burlas, inversión de control, etc.)