Existe uma maneira, no código ou com argumentos da JVM, de substituir o horário atual, conforme apresentado via System.currentTimeMillis
, além de alterar manualmente o relógio do sistema na máquina host?
Um pouco de fundo:
Temos um sistema que executa uma série de tarefas contábeis que giram grande parte de sua lógica em torno da data atual (ou seja, 1º do mês, 1º do ano, etc.)
Infelizmente, grande parte do código legado chama funções como new Date()
ou Calendar.getInstance()
, ambas eventualmente chamadas para System.currentTimeMillis
.
Para fins de teste, no momento, estamos presos à atualização manual do relógio do sistema para manipular a hora e a data em que o código pensa que o teste está sendo executado.
Então, minha pergunta é:
Existe uma maneira de substituir o que é retornado System.currentTimeMillis
? Por exemplo, para dizer à JVM para adicionar ou subtrair automaticamente algum deslocamento antes de retornar desse método?
Desde já, obrigado!
fonte
Respostas:
Eu recomendo fortemente que, em vez de mexer com o relógio do sistema, você morda o marcador e refatore esse código legado para usar um relógio substituível. Idealmente, isso deve ser feito com injeção de dependência, mas mesmo se você usasse um singleton substituível, obteria testabilidade.
Isso quase poderia ser automatizado com a pesquisa e substituição da versão singleton:
Calendar.getInstance()
porClock.getInstance().getCalendarInstance()
.new Date()
porClock.getInstance().newDate()
System.currentTimeMillis()
porClock.getInstance().currentTimeMillis()
(etc, conforme necessário)
Depois de dar o primeiro passo, você pode substituir o singleton por DI um pouco de cada vez.
fonte
java.time.Clock
classe "para permitir que relógios alternativos sejam conectados conforme e quando necessário".static
métodos, com certeza eles não são OO, mas injetar uma dependência sem estado cuja instância não opera em nenhum estado de instância não é realmente melhor; é apenas uma maneira elegante de disfarçar o que é efetivamente o comportamento "estático" de qualquer maneira.tl; dr
Sim.
Clock
No java.timeTemos uma nova solução para o problema de uma substituição de relógio conectável para facilitar o teste com valores falsos de data e hora. O pacote java.time no Java 8 inclui uma classe abstrata
java.time.Clock
, com um propósito explícito:Você pode conectar sua própria implementação de
Clock
, embora provavelmente encontre uma já feita para atender às suas necessidades. Para sua conveniência, o java.time inclui métodos estáticos para gerar implementações especiais. Essas implementações alternativas podem ser valiosas durante o teste.Cadência alterada
Os vários
tick…
métodos produzem relógios que incrementam o momento atual com uma cadência diferente.O padrão
Clock
relata um horário atualizado com a frequência de milissegundos no Java 8 e no Java 9 tão bom quanto os nanossegundos (dependendo do seu hardware). Você pode solicitar que o verdadeiro momento atual seja relatado com uma granularidade diferente.tickSeconds
- Incrementos em segundos inteirostickMinutes
- Incrementos em minutos inteirostick
- Incrementos peloDuration
argumento passado .Relógios falsos
Alguns relógios podem mentir, produzindo um resultado diferente do relógio do hardware do sistema operacional host.
fixed
- Relata um único momento imutável (sem incremento) como o momento atual.offset
- Relata o momento atual, mas mudou com oDuration
argumento passado .Por exemplo, prenda o primeiro momento do Natal mais cedo deste ano. em outras palavras, quando Papai Noel e suas renas fazem sua primeira parada . O fuso horário mais antigo atualmente parece estar
Pacific/Kiritimati
em+14:00
.Use esse relógio fixo especial para sempre retornar o mesmo momento. Chegamos ao primeiro momento do dia de Natal em Kiritimati , com o UTC mostrando um relógio de parede de catorze horas antes, às 10h da data anterior de 24 de dezembro.
Veja o código ao vivo em IdeOne.com .
Hora verdadeira, fuso horário diferente
Você pode controlar qual fuso horário é atribuído pela
Clock
implementação. Isso pode ser útil em alguns testes. Mas eu não recomendo isso no código de produção, onde você sempre deve especificar explicitamente o opcionalZoneId
ouZoneOffset
argumentos.Você pode especificar que o UTC seja a zona padrão.
Você pode especificar qualquer fuso horário específico. Especifique um nome próprio fuso horário no formato
continent/region
, comoAmerica/Montreal
,Africa/Casablanca
, ouPacific/Auckland
. Nunca utilize o 3-4 letras, comoEST
ouIST
como eles são não zonas verdadeiros tempo, não padronizados, e nem mesmo única (!).É possível especificar o fuso horário padrão atual da JVM deve ser o padrão para um
Clock
objeto específico .Execute este código para comparar. Observe que todos relatam o mesmo momento, o mesmo ponto na linha do tempo. Eles diferem apenas no tempo do relógio de parede ; em outras palavras, três maneiras de dizer a mesma coisa, três maneiras de exibir o mesmo momento.
America/Los_Angeles
era a zona padrão atual da JVM no computador que executou esse código.A
Instant
classe está sempre no UTC, por definição. Portanto, esses trêsClock
usos relacionados à zona têm exatamente o mesmo efeito.Relógio padrão
A implementação usada por padrão para
Instant.now
é a retornada porClock.systemUTC()
. Esta é a implementação usada quando você não especifica aClock
. Veja você mesmo no código-fonte Java 9 de pré-lançamento paraInstant.now
.O padrão
Clock
paraOffsetDateTime.now
eZonedDateTime.now
éClock.systemDefaultZone()
. Veja o código fonte .O comportamento das implementações padrão mudou entre o Java 8 e o Java 9. No Java 8, o momento atual é capturado com uma resolução apenas em milissegundos, apesar da capacidade das classes de armazenar uma resolução de nanossegundos . O Java 9 traz uma nova implementação capaz de capturar o momento atual com uma resolução de nanossegundos - dependendo, é claro, da capacidade do relógio do hardware do computador.
Sobre java.time
A estrutura java.time é integrada ao Java 8 e posterior. Essas classes suplantar os velhos problemáticos legados aulas de data e hora, como
java.util.Date
,Calendar
, eSimpleDateFormat
.Para saber mais, consulte o Tutorial do Oracle . E procure Stack Overflow para muitos exemplos e explicações. A especificação é JSR 310 .
O projeto Joda-Time , agora em modo de manutenção , aconselha a migração para as classes java.time .
Você pode trocar objetos java.time diretamente com seu banco de dados. Use um driver JDBC compatível com JDBC 4.2 ou posterior. Não há necessidade de strings, não há necessidade de
java.sql.*
classes. O Hibernate 5 e o JPA 2.2 suportam java.time .Onde obter as classes java.time?
fonte
Como disse Jon Skeet :
Então aqui vai (presumindo que você acabou substituído todo o seu
new Date()
comnew DateTime().toDate()
)Se você deseja importar uma biblioteca que possui uma interface (consulte o comentário de Jon abaixo), basta usar o Relógio do Prevayler , que fornecerá implementações e também a interface padrão. A jarra cheia tem apenas 96kB, portanto não deve custar muito ...
fonte
Embora o uso de algum padrão DateFactory pareça bom, ele não cobre as bibliotecas que você não pode controlar - imagine a anotação Validation @Past com a implementação baseada em System.currentTimeMillis (existe).
É por isso que usamos o jmockit para zombar da hora do sistema diretamente:
Como não é possível obter o valor desmembrado original de millis, usamos o nano timer - isso não está relacionado ao relógio de parede, mas o tempo relativo é suficiente aqui:
Há um problema documentado: com o HotSpot, o tempo volta ao normal após várias chamadas - eis o relatório do problema: http://code.google.com/p/jmockit/issues/detail?id=43
Para superar isso, precisamos ativar uma otimização específica do HotSpot - execute a JVM com esse argumento
-XX:-Inline
.Embora isso possa não ser perfeito para produção, é adequado para testes e é absolutamente transparente para aplicativos, especialmente quando o DataFactory não faz sentido para os negócios e é introduzido apenas devido a testes. Seria bom ter a opção JVM embutida para ser executada em um tempo diferente. Pena que isso não é possível sem hacks como esse.
A história completa está no meu blog aqui: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/
A classe prática completa SystemTimeShifter é fornecida na postagem. A classe pode ser usada em seus testes, ou pode ser usada como a primeira classe principal antes da sua classe principal real com muita facilidade, a fim de executar seu aplicativo (ou mesmo todo o servidor de aplicativos) em um momento diferente. Obviamente, isso é destinado principalmente para fins de teste, não para o ambiente de produção.
EDIT Julho de 2014: O JMockit mudou muito recentemente e você deve usar o JMockit 1.0 para usá-lo corretamente (IIRC). Definitivamente não é possível atualizar para a versão mais recente, onde a interface é completamente diferente. Eu estava pensando em incluir apenas as coisas necessárias, mas como não precisamos disso em nossos novos projetos, não estou desenvolvendo nada.
fonte
Powermock funciona muito bem. Apenas usei para zombar
System.currentTimeMillis()
.fonte
@PrepareForTest
anotação na classe de teste).System.currentTimeMillis
qualquer lugar do seu caminho de classe (qualquer lib), você deve verificar todas as classes. Foi isso que eu quis dizer, nada mais. O ponto é que você zomba desse comportamento no lado do chamador ("você diz especificamente"). Isso é bom para testes simples, mas não para testes em que você não pode ter certeza do que chama esse método de onde (por exemplo, testes de componentes mais complexos com as bibliotecas envolvidas). Isso não significa que o Powermock esteja errado, apenas significa que você não pode usá-lo para esse tipo de teste.Use Programação Orientada a Aspectos (AOP, por exemplo, AspectJ) para criar a classe System para retornar um valor predefinido que você pode definir em seus casos de teste.
Ou tecer as classes de aplicativos para redirecionar a chamada para
System.currentTimeMillis()
ounew Date()
para outra classe de utilidade de sua preferência.A tecelagem de classes do sistema (
java.lang.*
) é um pouco mais complicada e você pode precisar executar tecelagem offline para o rt.jar e usar um JDK / rt.jar separado para seus testes.É chamado de tecelagem binária e também existem ferramentas especiais para executar a tecelagem de classes do sistema e contornar alguns problemas com isso (por exemplo, inicializar a VM pode não funcionar)
fonte
Realmente não há uma maneira de fazer isso diretamente na VM, mas você pode definir algo programaticamente a hora do sistema na máquina de teste. A maioria (todos?) Do SO possui comandos de linha de comando para fazer isso.
fonte
date
etime
. No Linux, odate
comandoUma maneira de substituir a hora atual do sistema para fins de teste JUnit em um aplicativo da web Java 8 com EasyMock, sem Joda Time e sem PowerMock.
Aqui está o que você precisa fazer:
O que precisa ser feito na classe testada
Passo 1
Adicione um novo
java.time.Clock
atributo à classe testadaMyService
e verifique se o novo atributo será inicializado corretamente com os valores padrão com um bloco de instanciação ou um construtor:Passo 2
Injete o novo atributo
clock
no método que solicita uma data e hora atuais. Por exemplo, no meu caso, tive que verificar se uma data armazenada no banco de dados havia acontecido antesLocalDateTime.now()
, com a qual substituíLocalDateTime.now(clock)
, da seguinte forma:O que precisa ser feito na aula de teste
etapa 3
Na classe de teste, crie um objeto de relógio simulado e injete-o na instância da classe testada antes de chamar o método testado e
doExecute()
, em seguida, redefina-o imediatamente depois, da seguinte forma:Verifique no modo de depuração e você verá que a data de 3 de fevereiro de 2017 foi injetada corretamente na
myService
instância e usada na instrução de comparação e, em seguida, foi redefinida corretamente para a data atual cominitDefaultClock()
.fonte
Na minha opinião, apenas uma solução não invasiva pode funcionar. Especialmente se você tiver bibliotecas externas e uma grande base de códigos herdada, não há uma maneira confiável de zombar do tempo.
JMockit ... funciona apenas por um número restrito de vezes
O PowerMock & Co ... precisa simular os clientes para System.currentTimeMillis (). Novamente uma opção invasiva.
A partir disso, vejo apenas a abordagem javaagent ou aop mencionada como transparente para todo o sistema. Alguém já fez isso e poderia apontar para uma solução desse tipo?
@jarnbjo: você poderia mostrar um pouco do código javaagent, por favor?
fonte
Se você estiver executando o Linux, poderá usar a ramificação principal do libfaketime ou no momento do teste do commit 4ce2835 .
Simplesmente defina a variável de ambiente com o tempo que você gostaria de zombar do seu aplicativo java e execute-o usando ld-preloading:
A segunda variável de ambiente é fundamental para aplicativos java, que de outra forma seriam congelados. Requer o ramo principal do libfaketime no momento da redação.
Se você quiser alterar o horário de um serviço gerenciado pelo systemd, adicione o seguinte às substituições de arquivos da sua unidade, por exemplo, para elasticsearch, isso seria
/etc/systemd/system/elasticsearch.service.d/override.conf
:Não se esqueça de recarregar o systemd usando o `systemctl daemon-reload
fonte
Se você quiser zombar do método que tem
System.currentTimeMillis()
argumento, pode passaranyLong()
da classe Matchers como argumento.PS Consigo executar meu caso de teste com sucesso usando o truque acima e apenas para compartilhar mais detalhes sobre o meu teste que estou usando as estruturas PowerMock e Mockito.
fonte