Pregunta

Tengo un código (C#) que se basa en la fecha de hoy para calcular correctamente las cosas en el futuro.Si uso la fecha de hoy en la prueba, tengo que repetir el cálculo en la prueba, lo cual no me parece correcto.¿Cuál es la mejor manera de establecer la fecha en un valor conocido dentro de la prueba para poder probar que el resultado es un valor conocido?

¿Fue útil?

Solución

Mi preferencia es que las clases que usan el tiempo realmente dependan de una interfaz, como

interface IClock
{
    DateTime Now { get; } 
}

Con una implementación concreta

class SystemClock: IClock
{
     DateTime Now { get { return DateTime.Now; } }
}

Luego, si lo desea, puede proporcionar cualquier otro tipo de reloj que desee para realizar pruebas, como

class StaticClock: IClock
{
     DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } }
}

Puede haber algunos gastos generales al proporcionar el reloj a la clase que depende de él, pero eso podría manejarse mediante cualquier cantidad de soluciones de inyección de dependencia (usando un contenedor de Inversión de Control, una simple inyección de constructor/definidor antiguo, o incluso un Patrón de puerta de enlace estática).

Otros mecanismos para entregar un objeto o método que proporcione los tiempos deseados también funcionan, pero creo que la clave es evitar restablecer el reloj del sistema, ya que eso solo introducirá problemas en otros niveles.

Además, usando DateTime.Now e incluirlo en sus cálculos no sólo no le parece correcto: le priva de la capacidad de probar momentos concretos, por ejemplo, si descubre un error que sólo ocurre cerca del límite de medianoche o los martes.Usar la hora actual no le permitirá probar esos escenarios.O al menos no cuando quieras.

Otros consejos

Ayende Rahien usos un método estático que es bastante simple...

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

Creo que crear una clase de reloj separada para algo simple como obtener la fecha actual es un poco excesivo.

Puede pasar la fecha de hoy como parámetro para poder ingresar una fecha diferente en la prueba.Esto tiene el beneficio adicional de hacer que su código sea más flexible.

Usar Microsoft Fakes para crear una corrección es una manera realmente fácil de hacerlo.Supongamos que tengo la siguiente clase:

public class MyClass
{
    public string WhatsTheTime()
    {
        return DateTime.Now.ToString();
    }

}

En Visual Studio 2012, puede agregar un ensamblaje Fakes a su proyecto de prueba haciendo clic derecho en el ensamblaje para el que desea crear Fakes/Shims y seleccionando "Agregar ensamblaje Fakes".

Adding Fakes Assembly

Finalmente, así es como se vería la clase de prueba:

using System;
using ConsoleApplication11;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DateTimeTest
{
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestWhatsTheTime()
    {

        using(ShimsContext.Create()){

            //Arrange
            System.Fakes.ShimDateTime.NowGet =
            () =>
            { return new DateTime(2010, 1, 1); };

            var myClass = new MyClass();

            //Act
            var timeString = myClass.WhatsTheTime();

            //Assert
            Assert.AreEqual("1/1/2010 12:00:00 AM",timeString);

        }
    }
}
}

La clave para una prueba unitaria exitosa es desacoplamiento.Debe separar su código interesante de sus dependencias externas, para que pueda probarse de forma aislada.(Afortunadamente, el desarrollo basado en pruebas produce código desacoplado).

En este caso, su externo es el DateTime actual.

Mi consejo aquí es extraer la lógica que trata con DateTime a un nuevo método o clase o lo que tenga sentido en su caso, y pasar DateTime.Ahora, su prueba unitaria puede pasar un DateTime arbitrario para producir resultados predecibles.

Otro que usa Microsoft Moles (Marco de aislamiento para .NET).

MDateTime.NowGet = () => new DateTime(2000, 1, 1);

Moles permite reemplazar cualquier método .NET con un delegado.Moles admite métodos estáticos o no virtuales.Moles se basa en el Profiler de PEX.

Sugeriría usar el patrón IDisposable:

[Test] 
public void CreateName_AddsCurrentTimeAtEnd() 
{
    using (Clock.NowIs(new DateTime(2010, 12, 31, 23, 59, 00)))
    {
        string name = new ReportNameService().CreateName(...);
        Assert.AreEqual("name 2010-12-31 23:59:00", name);
    } 
}

Aquí se describe en detalle:http://www.lesnikowski.com/blog/index.php/testing-datetime-now/

Respuesta sencilla:deshazte de System.DateTime :) En su lugar, usa NodaTime y su biblioteca de pruebas: NodaTime.Prueba.

Otras lecturas:

Podrías inyectar la clase (mejor:método/delegar) usas para DateTime.Now en la clase que se está examinando.Tener DateTime.Now sea ​​un valor predeterminado y solo configúrelo en la prueba como un método ficticio que devuelva un valor constante.

EDITAR: Lo que dijo Blair Conrad (tiene algún código para mirar).Excepto que tiendo a preferir a los delegados para esto, ya que no saturan tu jerarquía de clases con cosas como IClock...

Me enfrenté a esta situación con tanta frecuencia que creé un nuget simple que expone Ahora propiedad a través de la interfaz.

public interface IDateTimeTools
{
    DateTime Now { get; }
}

Por supuesto, la implementación es muy sencilla.

public class DateTimeTools : IDateTimeTools
{
    public DateTime Now => DateTime.Now;
}

Entonces, después de agregar nuget a mi proyecto, puedo usarlo en las pruebas unitarias.

enter image description here

Puede instalar el módulo directamente desde la GUI Nuget Package Manager o usando el comando:

Install-Package -Id DateTimePT -ProjectName Project

Y el código para Nuget es aquí.

El ejemplo de uso con Autofac se puede encontrar aquí.

¿Ha considerado utilizar la compilación condicional para controlar lo que sucede durante la depuración/implementación?

p.ej.

DateTime date;
#if DEBUG
  date = new DateTime(2008, 09, 04);
#else
  date = DateTime.Now;
#endif

De lo contrario, desea exponer la propiedad para poder manipularla, todo esto es parte del desafío de escribir. comprobable código, que es algo con lo que estoy luchando actualmente :D

Editar

Una gran parte de mí preferiría El enfoque de Blair.Esto le permite "conectar en caliente" partes del código para ayudar en las pruebas.Todo sigue el principio de diseño. encapsular lo que varía El código de prueba no es diferente del código de producción, simplemente nadie lo ve externamente.

Sin embargo, la creación de una interfaz puede parecer mucho trabajo para este ejemplo (por eso opté por la compilación condicional).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top