c# unit-testing datetime mstest systemtime

c# - Prueba unitaria: DateTime.Now



unit-testing mstest (19)

Tengo algunas pruebas de unidad que esperan que la ''hora actual'' sea diferente a DateTime.Now y no quiero cambiar la hora de la computadora, obviamente.

¿Cuál es la mejor estrategia para lograr esto?


Agregue un ensamblaje falso para Sistema (haga clic derecho en Referencia del sistema => Agregar ensamblaje falso).

Y escriba en su método de prueba:

using (ShimsContext.Create()) { System.Fakes.ShimDateTime.NowGet = () => new DateTime(2014, 3, 10); MethodThatUsesDateTimeNow(); }


Al usar ITimeProvider nos vimos obligados a llevarlo a un proyecto común compartido especial al que se debe hacer referencia desde el resto de otros proyectos. Pero esto complicó el control de las dependencias .

Buscamos ITimeProvider en .NET framework. Buscamos el paquete NuGet y encontramos one que no puede funcionar con DateTimeOffset .

Entonces, creamos nuestra propia solución, que solo depende de los tipos de la biblioteca estándar. Estamos usando una instancia de Func<DateTimeOffset> .

Cómo utilizar

public class ThingThatNeedsTimeProvider { private readonly Func<DateTimeOffset> now; private int nextId; public ThingThatNeedsTimeProvider(Func<DateTimeOffset> now) { this.now = now; this.nextId = 1; } public (int Id, DateTimeOffset CreatedAt) MakeIllustratingTuple() { return (nextId++, now()); } }

Cómo registrarse

Autofac

builder.RegisterInstance<Func<DateTimeOffset>>(() => DateTimeOffset.Now);

( Para futuros editores: agregue sus casos aquí ).

Cómo probar la unidad

public void MakeIllustratingTuple_WhenCalled_FillsCreatedAt() { DateTimeOffset expected = CreateRandomDateTimeOffset(); DateTimeOffset StubNow() => expected; var thing = new ThingThatNeedsTimeProvider(StubNow); var (_, actual) = thing.MakeIllustratingTuple(); Assert.AreEqual(expected, actual); }


Aquí está mi respuesta a esta pregunta. Combino el patrón ''Contexto Ambiental'' con IDisposable. Entonces puede usar DateTimeProvider.Current en su código de programa normal y en la prueba anula el alcance con una instrucción using.

using System; using System.Collections.Immutable; namespace ambientcontext { public abstract class DateTimeProvider : IDisposable { private static ImmutableStack<DateTimeProvider> stack = ImmutableStack<DateTimeProvider>.Empty.Push(new DefaultDateTimeProvider()); protected DateTimeProvider() { if (this.GetType() != typeof(DefaultDateTimeProvider)) stack = stack.Push(this); } public static DateTimeProvider Current => stack.Peek(); public abstract DateTime Today { get; } public abstract DateTime Now {get; } public void Dispose() { if (this.GetType() != typeof(DefaultDateTimeProvider)) stack = stack.Pop(); } // Not visible Default Implementation private class DefaultDateTimeProvider : DateTimeProvider { public override DateTime Today => DateTime.Today; public override DateTime Now => DateTime.Now; } } }

Aquí se explica cómo usar el DateTimeProvider anterior dentro de un Unit-Test

using System; using Xunit; namespace ambientcontext { public class TestDateTimeProvider { [Fact] public void TestDateTime() { var actual = DateTimeProvider.Current.Today; var expected = DateTime.Today; Assert.Equal<DateTime>(expected, actual); using (new MyDateTimeProvider(new DateTime(2012,12,21))) { Assert.Equal(2012, DateTimeProvider.Current.Today.Year); using (new MyDateTimeProvider(new DateTime(1984,4,4))) { Assert.Equal(1984, DateTimeProvider.Current.Today.Year); } Assert.Equal(2012, DateTimeProvider.Current.Today.Year); } // Fall-Back to Default DateTimeProvider Assert.Equal<int>(expected.Year, DateTimeProvider.Current.Today.Year); } private class MyDateTimeProvider : DateTimeProvider { private readonly DateTime dateTime; public MyDateTimeProvider(DateTime dateTime):base() { this.dateTime = dateTime; } public override DateTime Today => this.dateTime.Date; public override DateTime Now => this.dateTime; } } }


En cuanto a la respuesta de @crabcrusherclamcollector, existe un problema al usar ese enfoque en las consultas de EF (System.NotSupportedException: LINQ to Entities no admite el tipo de nodo de expresión LINQ ''Invoke''). Modifiqué la implementación a eso:

public static class SystemTime { private static Func<DateTime> UtcNowFunc = () => DateTime.UtcNow; public static void SetDateTime(DateTime dateTimeNow) { UtcNowFunc = () => dateTimeNow; } public static void ResetDateTime() { UtcNowFunc = () => DateTime.UtcNow; } public static DateTime UtcNow { get { DateTime now = UtcNowFunc.Invoke(); return now; } } }


Estas son todas buenas respuestas, esto es lo que hice en un proyecto diferente:

Uso:

Obtenga la fecha REAL de hoy

var today = SystemTime.Now().Date;

En lugar de usar DateTime.Now, necesita utilizar SystemTime.Now() ... No es un cambio difícil, pero esta solución podría no ser ideal para todos los proyectos.

Tiempo de viaje (Vamos a ir 5 años en el futuro)

SystemTime.SetDateTime(today.AddYears(5));

Get Our Fake "hoy" (será de 5 años a partir de "hoy")

var fakeToday = SystemTime.Now().Date;

Restablecer la fecha

SystemTime.ResetDateTime();

/// <summary> /// Used for getting DateTime.Now(), time is changeable for unit testing /// </summary> public static class SystemTime { /// <summary> Normally this is a pass-through to DateTime.Now, but it can be overridden with SetDateTime( .. ) for testing or debugging. /// </summary> public static Func<DateTime> Now = () => DateTime.Now; /// <summary> Set time to return when SystemTime.Now() is called. /// </summary> public static void SetDateTime(DateTime dateTimeNow) { Now = () => dateTimeNow; } /// <summary> Resets SystemTime.Now() to return DateTime.Now. /// </summary> public static void ResetDateTime() { Now = () => DateTime.Now; } }


La mejor estrategia es ajustar la hora actual en una abstracción e inyectar esa abstracción en el consumidor .

Alternativamente , también puede definir una abstracción de tiempo como un Contexto Ambiental :

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

Esto te permitirá consumirlo así:

var now = TimeProvider.Current.UtcNow;

En una prueba unitaria, puede reemplazar TimeProvider.Current con un objeto Test Double / Mock. Ejemplo de uso de Moq:

var timeMock = new Mock<TimeProvider>(); timeMock.SetupGet(tp => tp.UtcNow).Returns(new DateTime(2010, 3, 11)); TimeProvider.Current = timeMock.Object;

Sin embargo, cuando se prueban unidades con estado estático, recuerde siempre desmontar su dispositivo llamando a TimeProvider.ResetToDefault() .


Me encontré con este mismo problema pero encontré un proyecto de investigación de Microsoft que resuelve este problema.

research.microsoft.com/en-us/projects/moles

Moles es un marco ligero para pruebas y desvíos en .NET que se basa en delegados. Los moles se pueden usar para desviar cualquier método .NET, incluidos los métodos no virtuales / estáticos en tipos sellados

// Let''s detour DateTime.Now MDateTime.NowGet = () => new DateTime(2000,1, 1); if (DateTime.Now == new DateTime(2000, 1, 1); { throw new Exception("Wahoo we did it!"); }

El código de muestra se modificó desde el original.

Hice lo que otros sugirieron y resumí el DateTime en un proveedor. Simplemente me sentí mal y sentí que era demasiado solo para probar. Voy a implementar esto en mi proyecto personal esta noche.


Me sorprende que nadie haya sugerido una de las formas más obvias de hacerlo:

public class TimeDependentClass { public void TimeDependentMethod(DateTime someTime) { if (GetCurrentTime() > someTime) DoSomething(); } protected virtual DateTime GetCurrentTime() { return DateTime.Now; // or UtcNow } }

Entonces puede simplemente anular este método en su prueba doble.

También me gusta inyectar una clase TimeProvider en algunos casos, pero para otros, esto es más que suficiente. Probablemente, yo preferiría la versión TimeProvider si necesita reutilizarla en varias clases.

EDITAR: Para cualquier persona interesada, esto se llama agregar una "costura" a su clase, un punto donde puede enganchar su comportamiento para modificarlo (para propósitos de prueba u otros) sin tener que cambiar el código en la clase.


Para probar un código que depende de System.DateTime , el system.dll debe ser burlado.

Hay dos marcos que conozco que hacen esto. Microsoft falsifica y Smocks .

Las falsificaciones de Microsoft requieren ultimatum de visual studio 2012 y funciona directamente de compton.

Smocks es una fuente abierta y muy fácil de usar. Se puede descargar usando NuGet.

A continuación, se muestra una simulación de System.DateTime :

Smock.Run(context => { context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1)); // Outputs "2000" Console.WriteLine(DateTime.Now.Year); });


Simulacros de objetos.

Un DateTime simulado que devuelve un Ahora que es apropiado para su prueba.


Tal vez una solución menos profesional pero más simple podría ser un parámetro DateTime en el método de consumidor. Por ejemplo, en lugar de hacer un método como SampleMethod, make SampleMethod1 con parameter.Testing de SampleMethod1 es más fácil

public void SampleMethod() { DateTime anotherDateTime = DateTime.Today.AddDays(-10); if ((DateTime.Now-anotherDateTime).TotalDays>10) { } } public void SampleMethod1(DateTime dateTimeNow) { DateTime anotherDateTime = DateTime.Today.AddDays(-10); if ((dateTimeNow - anotherDateTime).TotalDays > 10) { } }


Tengo el mismo problema, pero estaba pensando que no deberíamos usar las cosas de fecha y hora establecidas en la misma clase. porque podría llevar a un mal uso un día. así que he usado el proveedor como

public class DateTimeProvider { protected static DateTime? DateTimeNow; protected static DateTime? DateTimeUtcNow; public DateTime Now { get { return DateTimeNow ?? System.DateTime.Now; } } public DateTime UtcNow { get { return DateTimeUtcNow ?? System.DateTime.UtcNow; } } public static DateTimeProvider DateTime { get { return new DateTimeProvider(); } } protected DateTimeProvider() { } }

Para las pruebas, en el proyecto de prueba hizo un ayudante que se ocupará de las cosas establecidas,

public class MockDateTimeProvider : DateTimeProvider { public static void SetNow(DateTime now) { DateTimeNow = now; } public static void SetUtcNow(DateTime utc) { DateTimeUtcNow = utc; } public static void RestoreAsDefault() { DateTimeNow = null; DateTimeUtcNow = null; } }

en el código

var dateTimeNow = DateTimeProvider.DateTime.Now //not DateTime.Now var dateTimeUtcNow = DateTimeProvider.DateTime.UtcNow //not DateTime.UtcNow

y en las pruebas

[Test] public void Mocked_Now() { DateTime now = DateTime.Now; MockDateTimeProvider.SetNow(now); //set to mock Assert.AreEqual(now, DateTimeProvider.DateTime.Now); Assert.AreNotEqual(now, DateTimeProvider.DateTime.UtcNow); } [Test] public void Mocked_UtcNow() { DateTime utcNow = DateTime.UtcNow; MockDateTimeProvider.SetUtcNow(utcNow); //set to mock Assert.AreEqual(utcNow, DateTimeProvider.DateTime.UtcNow); Assert.AreNotEqual(utcNow, DateTimeProvider.DateTime.Now); }

Pero hay que recordar una cosa, en algún momento el DateTime real y DateTime del proveedor no actúa igual

[Test] public void Now() { Assert.AreEqual(DateTime.Now.Kind, DateTimeProvider.DateTime.Now.Kind); Assert.LessOrEqual(DateTime.Now, DateTimeProvider.DateTime.Now); Assert.LessOrEqual(DateTimeProvider.DateTime.Now - DateTime.Now, TimeSpan.FromMilliseconds(1)); }

Supuse que la deferencia sería TimeSpan.FromMilliseconds máximo (0.00002) . Pero la mayoría de las veces es incluso menos

Encuentre la muestra en MockSamples


Una buena práctica es cuando DateTimeProvider implementa IDisposable.

public class DateTimeProvider : IDisposable { [ThreadStatic] private static DateTime? _injectedDateTime; private DateTimeProvider() { } /// <summary> /// Gets DateTime now. /// </summary> /// <value> /// The DateTime now. /// </value> public static DateTime Now { get { return _injectedDateTime ?? DateTime.Now; } } /// <summary> /// Injects the actual date time. /// </summary> /// <param name="actualDateTime">The actual date time.</param> public static IDisposable InjectActualDateTime(DateTime actualDateTime) { _injectedDateTime = actualDateTime; return new DateTimeProvider(); } public void Dispose() { _injectedDateTime = null; } }

A continuación, puede inyectar su DateTime falso para pruebas unitarias

using (var date = DateTimeProvider.InjectActualDateTime(expectedDateTime)) { var bankAccount = new BankAccount(); bankAccount.DepositMoney(600); var lastTransaction = bankAccount.Transactions.Last(); Assert.IsTrue(expectedDateTime.Equals(bankAccount.Transactions[0].TransactionDate)); }

Ver ejemplo Ejemplo de DateTimeProvider


Una forma limpia de hacerlo es inyectar VirtualTime. Te permite controlar el tiempo. Primero instala VirtualTime

Install-Package VirtualTime

Eso permite, por ejemplo, hacer que el tiempo se mueva 5 veces más rápido en todas las llamadas a DateTime.Now o UtcNow

var DateTime = DateTime.Now.ToVirtualTime(5);

Para hacer que el tiempo se mueva más lento, por ejemplo, 5 veces más lento

var DateTime = DateTime.Now.ToVirtualTime(0.5);

Para hacer que el tiempo se pare, hazlo

var DateTime = DateTime.Now.ToVirtualTime(0);

Retroceder en el tiempo aún no ha sido probado

Aquí hay una prueba de muestra:

[TestMethod] public void it_should_make_time_move_faster() { int speedOfTimePerMs = 1000; int timeToPassMs = 3000; int expectedElapsedVirtualTime = speedOfTimePerMs * timeToPassMs; DateTime whenTimeStarts = DateTime.Now; ITime time = whenTimeStarts.ToVirtualTime(speedOfTimePerMs); Thread.Sleep(timeToPassMs); DateTime expectedTime = DateTime.Now.AddMilliseconds(expectedElapsedVirtualTime - timeToPassMs); DateTime virtualTime = time.Now; Assert.IsTrue(TestHelper.AreEqualWithinMarginOfError(expectedTime, virtualTime, MarginOfErrorMs)); }

Puedes ver más pruebas aquí:

https://github.com/VirtualTime/VirtualTime/blob/master/VirtualTimeLib.Tests/when_virtual_time_is_used.cs

Lo que DateTime.Now.ToVirtualTime le ofrece es una instancia de ITime que pasa a un método / clase que depende de ITime. algunos DateTime.Now.ToVirtualTime se configuran en el contenedor DI de su elección

Aquí hay otro ejemplo de inyección en un contrustor de clase

public class AlarmClock { private ITime DateTime; public AlarmClock(ITime dateTime, int numberOfHours) { DateTime = dateTime; SetTime = DateTime.UtcNow.AddHours(numberOfHours); Task.Run(() => { while (!IsAlarmOn) { IsAlarmOn = (SetTime - DateTime.UtcNow).TotalMilliseconds < 0; } }); } public DateTime SetTime { get; set; } public bool IsAlarmOn { get; set; } } [TestMethod] public void it_can_be_injected_as_a_dependency() { //virtual time has to be 1000*3.75 faster to get to an hour //in 1000 ms real time var dateTime = DateTime.Now.ToVirtualTime(1000 * 3.75); var numberOfHoursBeforeAlarmSounds = 1; var alarmClock = new AlarmClock(dateTime, numberOfHoursBeforeAlarmSounds); Assert.IsFalse(alarmClock.IsAlarmOn); System.Threading.Thread.Sleep(1000); Assert.IsTrue(alarmClock.IsAlarmOn); }


Una nota especial sobre burlarse de DateTime.Now con TypeMock ...

El valor de DateTime.Now debe colocarse en una variable para burlarlo correctamente. Por ejemplo:

Esto no funciona:

if ((DateTime.Now - message.TimeOpened.Value) > new TimeSpan(1, 0, 0))

Sin embargo, esto hace:

var currentDateTime = DateTime.Now; if ((currentDateTime - message.TimeOpened.Value) > new TimeSpan(1, 0, 0))


Una opción alternativa que no se menciona es inyectar la hora actual al método dependiente:

public class DateTimeNowDependencyClass { ... public void ImplicitTimeDependencyMethod(Obj arg0) { this.TimeDependencyMethod(DateTime.Now, arg0); } internal void TimeDependencyMethod(DateTime now, Obj arg1) { ... } ... }

La variación interna está abierta a pruebas unitarias, paralelas o no.

Esto se basa en el principio de que ImplicitTimeDependencyMethod es "demasiado simple de romper" (ver: http://junit.sourceforge.net/doc/faq/faq.htm#best_3 ) por lo que no necesita ser incluido en la cobertura de prueba de la unidad. Aunque debe ser tocado en las pruebas de integración de todos modos.

Dependiendo del propósito de la clase, puede ser deseable que ambos métodos sean públicos de todos modos.


Usted tiene algunas opciones para hacerlo:

  1. Use el marco de simulación y use un DateTimeService (implemente una clase de envoltura pequeña e inyéctela al código de producción). La implementación de contenedor accederá a DateTime y en las pruebas podrá simular la clase contenedora.

  2. Use Typemock Isolator , puede simular DateTime.Now y no le pedirá que cambie el código bajo prueba.

  3. Use research.microsoft.com/en-us/projects/moles , también puede simular DateTime.Now y no requerirá cambios en el código de producción.

Algunos ejemplos:

Clase de envoltorio usando Moq:

[Test] public void TestOfDateTime() { var mock = new Mock<IDateTime>(); mock.Setup(fake => fake.Now) .Returns(new DateTime(2000, 1, 1)); var result = new UnderTest(mock.Object).CalculateSomethingBasedOnDate(); } public class DateTimeWrapper : IDateTime { public DateTime Now { get { return DateTime.Now; } } }

Fingir DateTime directamente con Isolator:

[Test] public void TestOfDateTime() { Isolate.WhenCalled(() => DateTime.Now).WillReturn(new DateTime(2000, 1, 1)); var result = new UnderTest().CalculateSomethingBasedOnDate(); }

Descargo de responsabilidad - Yo trabajo en Typemock


Lunares:

[Test] public void TestOfDateTime() { var firstValue = DateTime.Now; MDateTime.NowGet = () => new DateTime(2000,1,1); var secondValue = DateTime.Now; Assert(firstValue > secondValue); // would be false if ''moleing'' failed }

Descargo de responsabilidad - Yo trabajo en Moles


Un SystemClock seguro para SystemClock usa ThreadLocal<T> funciona muy bien para mí.

ThreadLocal<T> está disponible en .Net Framework v4.0 y superior.

/// <summary> /// Provides access to system time while allowing it to be set to a fixed <see cref="DateTime"/> value. /// </summary> /// <remarks> /// This class is thread safe. /// </remarks> public static class SystemClock { private static readonly ThreadLocal<Func<DateTime>> _getTime = new ThreadLocal<Func<DateTime>>(() => () => DateTime.Now); /// <inheritdoc cref="DateTime.Today"/> public static DateTime Today { get { return _getTime.Value().Date; } } /// <inheritdoc cref="DateTime.Now"/> public static DateTime Now { get { return _getTime.Value(); } } /// <inheritdoc cref="DateTime.UtcNow"/> public static DateTime UtcNow { get { return _getTime.Value().ToUniversalTime(); } } /// <summary> /// Sets a fixed (deterministic) time for the current thread to return by <see cref="SystemClock"/>. /// </summary> public static void Set(DateTime time) { if (time.Kind != DateTimeKind.Local) time = time.ToLocalTime(); _getTime.Value = () => time; } /// <summary> /// Resets <see cref="SystemClock"/> to return the current <see cref="DateTime.Now"/>. /// </summary> public static void Reset() { _getTime.Value = () => DateTime.Now; } }

Ejemplo de uso:

[TestMethod] public void Today() { SystemClock.Set(new DateTime(2015, 4, 3)); DateTime expectedDay = new DateTime(2015, 4, 2); DateTime yesterday = SystemClock.Today.AddDays(-1D); Assert.AreEqual(expectedDay, yesterday); SystemClock.Reset(); }