Pergunta

Eu tenho alguns testes de unidade que espera que o 'tempo atual' para ser diferente do DateTime.Now e eu não quero mudar a hora do computador, obviamente.

Qual é a melhor estratégia para conseguir isso?

Foi útil?

Solução

O melhor estratégia é embrulhar a hora atual em uma abstração e injetar que a abstração na consumidor.


Como alternativa , você também pode definir uma abstração tempo como um Contexto Ambient :

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

Isto irá permitir que você consumi-lo como este:

var now = TimeProvider.Current.UtcNow;

Em um teste de unidade, você pode substituir TimeProvider.Current com um objeto Teste Duplo / Mock. Exemplo usando Moq:

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

No entanto, quando a unidade de teste com o estado estático, lembre-se sempre lágrima pelo seu acessório chamando TimeProvider.ResetToDefault().

Outras dicas

Estas são todas boas respostas, este é o que eu fiz em um projeto diferente:

Uso:

Obter verdadeira data de hoje Hora

var today = SystemTime.Now().Date;

Em vez de usar DateTime.Now, você precisa usar SystemTime.Now() ... Não é mudança difícil, mas esta solução pode não ser ideal para todos os projetos.

Tempo Viajar (Lets go 5 anos no futuro)

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

Obter Nossa Falso "hoje" (será de 5 anos de 'hoje')

var fakeToday = SystemTime.Now().Date;

Redefinir a data

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

Moles:

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

Disclaimer - trabalho que em Moles

Você tem algumas opções para fazê-lo:

  1. Use zombando quadro e usar um DateTimeService (Implementar uma pequena classe de mensagens publicitárias e injetá-lo para o código de produção). A implementação wrapper acessar DateTime e nos testes que você vai ser capaz de zombar a classe wrapper.

  2. Use Typemock Isolador , pode falso DateTime.Now e não vai exigir que você altere o código sob teste.

  3. Use Moles , também pode falso DateTime.Now e não exigirá mudanças no código de produção.

Alguns exemplos:

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

Faking DateTime directamente utilizando Isolador:

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

     var result = new UnderTest().CalculateSomethingBasedOnDate();
}

Disclaimer - trabalho que a Typemock

Adicionar um falso montagem de sistema (clique direito sobre referência do Sistema => Adicionar falso montagem).

e escrever em seu método de teste:

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

Em relação à resposta @crabcrusherclamcollector há problema quando usando essa abordagem em consultas EF (System.NotSupportedException: O tipo de nó expressão LINQ 'Invoke' não é suportado no LINQ to Entities). Eu modifiquei implementação para que:

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

A SystemClock segmento seguro usando ThreadLocal<T> funciona muito bem para mim.

ThreadLocal<T> está disponível no v4.0 .Net Framework e 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;
    }
}

Exemplo 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();
}

Corri para este mesmo problema, mas encontrou um projeto de pesquisa da Microsoft que resolve esta questão.

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

Moles é um quadro leve para topos de teste e desvios em .NET que é baseado em delegados. Moles pode ser utilizada para se desviar qualquer método NET, incluindo métodos estáticos / não-virtuais em tipos seladas

// 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!");
}

O código de exemplo foi modificado a partir do original.

Eu tinha feito o que outros sugerido e abstraída a DateTime em um provedor. Só senti errado e eu senti que era muito, apenas para testes. Eu estou indo para implementar isso em meu projeto pessoal esta noite.

Para testar um código que depende do System.DateTime, o system.dll deve ser ridicularizado.

Existem dois quadro que eu conheço que faz isso. Microsoft finge e Smocks .

falsificações Microsoft exigem visual studio 2012 ultimato e trabalha em linha reta do Compton.

Smocks é uma fonte aberta e muito fácil de usar. Ele pode ser baixado usando NuGet.

A seguir mostra uma simulação da System.DateTime:

Smock.Run(context =>
{
  context.Setup(() => DateTime.Now).Returns(new DateTime(2000, 1, 1));

   // Outputs "2000"
   Console.WriteLine(DateTime.Now.Year);
});

Mock Objects.

A simulação DateTime que retorna uma Agora que é apropriado para seu teste.

Estou surpreso que ninguém tenha sugerido uma das maneiras mais óbvias para ir:

public class TimeDependentClass
{
    public void TimeDependentMethod(DateTime someTime)
    {
        if (GetCurrentTime() > someTime) DoSomething();
    }

    protected virtual DateTime GetCurrentTime()
    {
        return DateTime.Now; // or UtcNow
    }
}

Em seguida, você pode simplesmente substituir esse método em seu teste de casal.

Eu também tipo de como injetar uma classe TimeProvider em alguns casos, mas para os outros, isso é mais do que suficiente. Eu provavelmente favorecem a versão TimeProvider se você precisar reutilizar este em várias classes embora.

EDIT: Para quem estiver interessado, isso é chamado adição de uma "costura" para sua classe, a um ponto onde você pode ligar para ele do comportamento de modificá-lo (para fins ou não testar) sem realmente ter que mudar o código no classe.

Uma nota especial sobre zombando DateTime.Now com TypeMock ...

O valor da DateTime.Now deve ser colocado em uma variável para que isso seja ridicularizado corretamente. Por exemplo:

Isso não funciona:

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

No entanto, isso faz:

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

A boa prática é, quando 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; 
    } 
} 

Em seguida, você pode injetar sua DateTime falso para testes de unidade

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

Exemplo de DateTimeProvider

Uma forma limpa de fazer isso é para injetar VirtualTime. Ele permite que você controlar o tempo. Primeiro instale VirtualTime

Install-Package VirtualTime

Isso permite, por exemplo, o tempo make que move 5 vezes mais rápido em todas as chamadas para DateTime.Now ou UtcNow

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

Para fazer movimento de tempo mais lenta, por exemplo, 5 vezes mais lento fazer

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

Para fazer o tempo parar fazer

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

Mover-se para trás no tempo ainda não está testado

Aqui são um teste da amostra:

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

Você pode conferir mais testes aqui:

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

O que DateTime.Now.ToVirtualTime extensão dá-lhe é uma instância de iTime que você passar para um método / classe que depende iTime. alguns DateTime.Now.ToVirtualTime está configurado no recipiente DI de sua escolha

Aqui está outro exemplo injectar num contrustor classe

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

Eu tenho mesmo problema, mas eu estava pensando que não devemos usar o conjunto de data e hora as coisas na mesma classe. porque isso poderia levar a abusar um dia. então eu usei o provedor 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 os testes, pelo projeto de teste feito um auxiliar que vai lidar com conjunto de coisas,

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

no código

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

e em testes

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

Mas necessidade de lembrar uma coisa, em algum momento do verdadeiro DateTime e provedor de DateTime não age mesmo

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

Eu assumi a deferência seria máximas TimeSpan.FromMilliseconds (0,00002) . Mas a maior parte do tempo, é ainda menos

Encontre a amostra em MockSamples

Uma opção alternativa que não é mencionado é injetar o tempo atual para o método dependente:

public class DateTimeNowDependencyClass
{
    ...

    public void ImplicitTimeDependencyMethod(Obj arg0)
    {
        this.TimeDependencyMethod(DateTime.Now, arg0);
    }

    internal void TimeDependencyMethod(DateTime now, Obj arg1)
    {
        ...
    }

    ...
}

A variação interna é aberta para o teste de unidade, paralelas ou não.

Isto é baseado no princípio de que ImplicitTimeDependencyMethod é "simples demais para quebrar" (ver: http://junit.sourceforge.net/doc/faq/faq.htm#best_3 ) para que não precisam ser incluídos na cobertura de teste de unidade. Embora deve ser tocado em testes de integração de qualquer maneira.

Dependendo da finalidade classe pode ser desejável ter ambos os métodos de qualquer maneira pública.

Aqui está o meu anwer desta questão. Eu combino o padrão 'Contexto Ambient' com IDisposable. Então você pode usar o DateTimeProvider.Current em seu código de programa normal e no teste você substituir o escopo com uma instrução 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; 
    }
}
}

Aqui está como usar o acima DateTimeProvider dentro de uma Unidade-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;
        }
    }
}

Usando ITimeProvider fomos obrigados a levá-la em comum compartilhado especial projeto que deve ser referenciado do resto de outros projectos. Mas isso complicado o controle das dependências .

Nós procurou o ITimeProvider no framework .NET. Nós procurou o pacote NuGet, e encontrou um que não pode trabalhar com DateTimeOffset .

Assim que surgiu com a nossa própria solução, que depende apenas os tipos de biblioteca padrão. Estamos usando uma instância de Func<DateTimeOffset>.

Como usar

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

Como registrar

Autofac

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

( Para editores futuros: acrescentar os seus casos aqui ).

Como unidade de teste

public void MakeIllustratingTuple_WhenCalled_FillsCreatedAt()
{
    DateTimeOffset expected = CreateRandomDateTimeOffset();
    DateTimeOffset StubNow() => expected;
    var thing = new ThingThatNeedsTimeProvider(StubNow);

    var (_, actual) = thing.MakeIllustratingTuple();

    Assert.AreEqual(expected, actual);
}

Talvez menos profissional, mas solução mais simples poderia ser tornar um parâmetro DateTime no consumidor método.Para exemplo, em vez do método de fazer como SampleMethod, fazer SampleMethod1 com parameter.Testing de SampleMethod1 é mais 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)
        {

        }

    }

Estávamos usando um SYSTEMTIME objeto estático, mas funcionou em problemas para executar testes de unidade paralelas. Tentei usar a solução de Henk van Boeijen mas teve problemas entre segmentos assíncronos gerados, acabou usando usando AsyncLocal de uma maneira semelhante a este abaixo:

public static class Clock
{
    private static Func<DateTime> _utcNow = () => DateTime.UtcNow;

    static AsyncLocal<Func<DateTime>> _override = new AsyncLocal<Func<DateTime>>();

    public static DateTime UtcNow => (_override.Value ?? _utcNow)();

    public static void Set(Func<DateTime> func)
    {
        _override.Value = func;
    }

    public static void Reset()
    {
        _override.Value = null;
    }
}

https://gist.github.com/CraftyFella/42f459f7687b0b8b268fc311e6b4af08

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top