O que é uma boa maneira de substituir DateTime.Agora durante o teste?
-
09-06-2019 - |
Pergunta
Eu tenho alguns (C#) código que se baseia na data de hoje para calcular corretamente as coisas no futuro.Se eu usar a data de hoje, no ensaio, eu tenho que repetir o cálculo do teste, que não se sente bem.Qual é a melhor maneira de definir a data para um valor conhecido dentro do teste, para que eu possa testar o resultado é um valor conhecido?
Solução
A minha preferência é ter classes que usam o tempo, na verdade, dependem de uma interface, tais como
interface IClock
{
DateTime Now { get; }
}
Com uma implementação concreta
class SystemClock: IClock
{
DateTime Now { get { return DateTime.Now; } }
}
Então, se você quiser, você pode fornecer qualquer outro tipo de relógio que você deseja para o teste, como
class StaticClock: IClock
{
DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } }
}
Pode haver alguma sobrecarga na prestação de relógio para a classe que depende de ti, mas que pode ser manipulada por qualquer número de injeção de dependência soluções (usando uma Inversão de Controle de recipiente, velho e simples do construtor/montador de injeção, ou até mesmo um Estática Gateway Padrão).
Outros mecanismos de entrega de um objecto ou método que fornece desejado vezes também funciona, mas eu acho que o fundamental é evitar repor o relógio do sistema, que vai apresentar dor em outros níveis.
Além disso, usando DateTime.Now
e incluindo-a em seus cálculos, não apenas não se sente bem - ele rouba-lhe a capacidade para testar determinados momentos, por exemplo, se você descobrir um erro que só acontece perto de um limite de meia-noite, ou às terças-feiras.Usando o tempo atual não permitem que você teste os cenários.Ou pelo menos não sempre que você quiser.
Outras dicas
Ayende Rahien usa um método estático que é bastante simples...
public static class SystemTime
{
public static Func<DateTime> Now = () => DateTime.Now;
}
Eu acho que criar um separar classe de relógio para algo simples, como ter a data atual é um pouco exagero.
Você pode passar a data de hoje como um parâmetro, de forma que você pode inserir uma data diferente no teste.Isso tem o benefício adicional de tornar o código mais flexível.
Usando o Microsoft Fakes para criar um calço é realmente uma maneira fácil de fazer isso.Suponha que eu tinha a seguinte classe:
public class MyClass
{
public string WhatsTheTime()
{
return DateTime.Now.ToString();
}
}
No Visual Studio 2012, você pode adicionar uma Falsificação da assembleia para o seu projeto de teste com o botão direito sobre a montagem que você deseja criar Fakes/Correções para o e selecionando "Adicionar Fakes Assembly"
Finalmente, Aqui está o que a classe de teste seria:
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);
}
}
}
}
A chave para o sucesso de teste de unidade é dissociação.É preciso separar o código interessante a partir de suas dependências externas, de modo que ele pode ser testado isoladamente.(Felizmente, Test-Driven Development produz dissociado de código.)
Neste caso, o externo é a de data / hora actual.
Meu conselho aqui é extrair a lógica que lida com a data e hora de um novo método ou classe ou o que faz sentido no seu caso, e passar a data e hora em.Agora, o teste de unidade pode passar um arbitrário DateTime, para produzir resultados previsíveis.
Outra, usando o Microsoft Moles (Estrutura de isolamento para .NET).
MDateTime.NowGet = () => new DateTime(2000, 1, 1);
Moles permite substituir qualquer .NET método com um delegado.As toupeiras suporta estático ou não de métodos virtuais.As toupeiras depende do profiler do Pex.
Eu gostaria de sugerir o uso de IDisposable padrão:
[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);
}
}
Em detalhes descritos aqui:http://www.lesnikowski.com/blog/index.php/testing-datetime-now/
Resposta simples:Sistema de valas.Data / hora :) em Vez disso, use NodaTime e é biblioteca de testes: NodaTime.Teste.
Leitura adicional:
Você pode injetar a classe (melhor:método/delegado que você usa para DateTime.Now
na classe que está sendo testada.Tem DateTime.Now
ser um valor padrão e defina-a apenas em testes de um manequim método que retorna um valor constante.
EDITAR: O que Blair Conrad disse (ele tem algum código para olhar).Exceto, eu tendem a preferir os delegados para isso, como elas não ocuparem a sua hierarquia de classes com coisas como IClock
...
Eu enfrentei essa situação tantas vezes, que eu criados simples nuget que expõe Agora propriedade através da interface.
public interface IDateTimeTools
{
DateTime Now { get; }
}
A implementação é muito simples
public class DateTimeTools : IDateTimeTools
{
public DateTime Now => DateTime.Now;
}
Então, depois de adicionar o nuget para o meu projeto, eu posso usá-lo na unidade de testes
Você pode instalar o módulo de direito da interface de usuário do Gerenciador de Pacotes Nuget ou usando o comando:
Install-Package -Id DateTimePT -ProjectName Project
E o código para o Nuget é aqui.
O exemplo de uso com o Autofac pode ser encontrado aqui.
Você já pensou em usar a compilação condicional para controlar o que acontece durante a depuração/implementação?
exemplo:
DateTime date;
#if DEBUG
date = new DateTime(2008, 09, 04);
#else
date = DateTime.Now;
#endif
Se isso falhar, você quer expor a propriedade para que você possa manipulá-lo, tudo isso é parte do desafio de escrever testáveis o código, que é algo que eu estou atualmente wrestling mim :D
Editar
Uma grande parte de mim teria preferência Blair abordagem.Isso permite que você para de "hot plug" de partes do código para auxílio no teste.Tudo segue o princípio do design encapsular o que varia código de teste não é diferente para a produção de código, só que ninguém nunca vê-lo externamente.
A criação e a interface pode parecer um monte de trabalho para esse exemplo que (é por isso optei por compilação condicional).