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.
¿Has utilizado moq? (o cualquier otro marco de burla) http://code.google.com/p/moq/ – Magrangs
He solucionado el formateo de tu código. Por favor, compárelo con su versión para aprender a hacerlo correctamente. –
@Magrangs: como todos los otros marcos de burla "normales", Moq no puede simular métodos y propiedades estáticos como 'DateTime.Now'. –