使用没有 DependencyInjection 的 Calendar.getInstance() 测试自定义 DateUtilClass
Test custom DateUtilClass with Calendar.getInstance() without DependencyInjection
考虑以下 class:
public class DateUtils {
public static Date get31OfDecember2YearsAgo() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
int year2YearsAgo = calendar.get(Calendar.YEAR) - 2;
calendar.set(year2YearsAgo, Calendar.DECEMBER, 31);
return calendar.getTime();
}
}
你知道我如何测试这段代码吗?我在考虑一个 DateProvider,我可以在测试中注入和模拟它,但是因为我没有可用的依赖注入...
此代码被硬编码为使用 Calendar
和 Date
,因此您无法按原样对其进行真正的测试。话又说回来,public static
方法通常不打算进行测试;相反,它们应该非常简单,您只需查看它们并确定它们是否正确。
我强烈建议您放弃此 get31ofDecember2YearsAgo()
方法以及整个 DateUtils
class,并重构您的整个系统以开始使用正确设计的可实例化 class 用于日期和时间实用程序。这样做时,请使用最新的 java 8 时间 classes,或者,如果您不幸不得不使用 8 之前的某些版本的 java,那么至少使用 joda 时间库。
切勿在任何地方使用构思不当的Instant.now()
方法;相反,创建一个提供当前时间的界面,以便您可以正确模拟它以进行测试。
编辑
这里,来自我正在从事的一个项目:
import java.time.*;
/**
* Provides the notion of current time. Necessary, because without it,
* classes that deal with the current time cannot be tested.
*
* @author Michael Belivanakis (michael.gr)
*/
public interface TimekeepingDomain
{
/**
* Gets the current {@link Instant}. (UTC, of course.)
*
* @return the current {@link Instant}.
*/
Instant getCurrentInstant();
/**
* Gets the system {@link ZoneId}.
*
* @return the system {@link ZoneId}.
*/
ZoneId getSystemZoneId();
/**
* Sleeps until the given {@link Instant}.
*
* Returns immediately if the given {@link Instant} is in the past.
*
* @param instant the {@link Instant} to sleep until.
*/
void sleepUntil( Instant instant ) throws InterruptedException;
/**
* Sleeps for the given {@link Duration}.
*
* Returns immediately if the given {@link Duration} is negative.
*
* @param duration the {@link Duration} to sleep for.
*/
void sleepFor( Duration duration ) throws InterruptedException;
}
一个"real"计时域实现:
final TimekeepingDomain timekeepingDomain = new TimekeepingDomain()
{
@Override
public Instant getCurrentInstant()
{
return Instant.now();
}
@Override
public ZoneId getSystemZoneId()
{
return ZoneId.systemDefault();
}
@Override
public void sleepUntil( Instant instant ) throws InterruptedException
{
sleepFor( Duration.between( Instant.now(), instant ) );
}
@Override
public void sleepFor( Duration duration ) throws InterruptedException
{
if( duration.isNegative() )
return;
long milliseconds = duration.toMillis();
Thread.sleep( milliseconds );
}
};
一个"fake"计时域实现:
import java.time.*;
/**
* Fake {@link TimekeepingDomain} for testing.
*
* @author Michael Belivanakis (michael.gr)
*/
class FakeTimekeepingDomain implements TimekeepingDomain
{
private Instant currentInstant;
FakeTimekeepingDomain( Instant startTime )
{
currentInstant = startTime;
}
@Override
public Instant getCurrentInstant()
{
currentInstant = currentInstant.plusMillis( 1L );
return currentInstant;
}
@Override
public ZoneId getSystemZoneId()
{
return ZoneOffset.UTC;
}
@Override
public void sleepUntil( Instant instant )
{
if( instant.isAfter( currentInstant ) )
currentInstant = instant;
}
@Override
public void sleepFor( Duration duration )
{
if( !duration.isNegative() )
currentInstant = currentInstant.plus( duration );
}
}
实例化如下:
TimekeepingDomain timekeepingDomain =
new FakeTimekeepingDomain( Instant.parse( "2014-08-11T10:15:30.00Z" ) );
那么接下来会发生什么,你的 calculateSomethingRelativeToNow()
方法变成非静态的,包含它的 class 被传递一个 TimekeepingDomain
作为构造函数参数,这样你的方法就可以调用该接口以获取(您希望它认为的)当前时间。
在您的生产设备中,您将 class 传递给 TimekeepingDomain
的真实实现的引用,一切都像以前一样工作。
在你的测试装置中,你可以传递你的 class 一个 FakeTimekeepingDomain
的实例,它已经被实例化以假装 'current' 时间是某个固定时间,(在过去, 或未来,都没有关系,) 这样您就可以检查您的方法的结果,看看它实际上是预期的结果。您可以尝试各种固定时间,以检查各种结果。
本质上,这是手动完成的依赖注入,没有使用像 Spring
这样的重型框架来实现它。
您可以重构 class 如下:
public class DateUtils {
public static Date get31OfDecember2YearsAgo() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date()); // This line is surely redundant?
return get31OfDecember2YearsAgo(calendar);
}
static Date get31OfDecember2YearsAgo(Calendar calendar) {
int year2YearsAgo = calendar.get(Calendar.YEAR) - 2;
calendar.set(year2YearsAgo, Calendar.DECEMBER, 31);
return calendar.getTime();
}
}
这将允许您测试 package-private 变体并使用一些已知值来检查正确的结果。
考虑以下 class:
public class DateUtils {
public static Date get31OfDecember2YearsAgo() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
int year2YearsAgo = calendar.get(Calendar.YEAR) - 2;
calendar.set(year2YearsAgo, Calendar.DECEMBER, 31);
return calendar.getTime();
}
}
你知道我如何测试这段代码吗?我在考虑一个 DateProvider,我可以在测试中注入和模拟它,但是因为我没有可用的依赖注入...
此代码被硬编码为使用 Calendar
和 Date
,因此您无法按原样对其进行真正的测试。话又说回来,public static
方法通常不打算进行测试;相反,它们应该非常简单,您只需查看它们并确定它们是否正确。
我强烈建议您放弃此 get31ofDecember2YearsAgo()
方法以及整个 DateUtils
class,并重构您的整个系统以开始使用正确设计的可实例化 class 用于日期和时间实用程序。这样做时,请使用最新的 java 8 时间 classes,或者,如果您不幸不得不使用 8 之前的某些版本的 java,那么至少使用 joda 时间库。
切勿在任何地方使用构思不当的Instant.now()
方法;相反,创建一个提供当前时间的界面,以便您可以正确模拟它以进行测试。
编辑
这里,来自我正在从事的一个项目:
import java.time.*;
/**
* Provides the notion of current time. Necessary, because without it,
* classes that deal with the current time cannot be tested.
*
* @author Michael Belivanakis (michael.gr)
*/
public interface TimekeepingDomain
{
/**
* Gets the current {@link Instant}. (UTC, of course.)
*
* @return the current {@link Instant}.
*/
Instant getCurrentInstant();
/**
* Gets the system {@link ZoneId}.
*
* @return the system {@link ZoneId}.
*/
ZoneId getSystemZoneId();
/**
* Sleeps until the given {@link Instant}.
*
* Returns immediately if the given {@link Instant} is in the past.
*
* @param instant the {@link Instant} to sleep until.
*/
void sleepUntil( Instant instant ) throws InterruptedException;
/**
* Sleeps for the given {@link Duration}.
*
* Returns immediately if the given {@link Duration} is negative.
*
* @param duration the {@link Duration} to sleep for.
*/
void sleepFor( Duration duration ) throws InterruptedException;
}
一个"real"计时域实现:
final TimekeepingDomain timekeepingDomain = new TimekeepingDomain()
{
@Override
public Instant getCurrentInstant()
{
return Instant.now();
}
@Override
public ZoneId getSystemZoneId()
{
return ZoneId.systemDefault();
}
@Override
public void sleepUntil( Instant instant ) throws InterruptedException
{
sleepFor( Duration.between( Instant.now(), instant ) );
}
@Override
public void sleepFor( Duration duration ) throws InterruptedException
{
if( duration.isNegative() )
return;
long milliseconds = duration.toMillis();
Thread.sleep( milliseconds );
}
};
一个"fake"计时域实现:
import java.time.*;
/**
* Fake {@link TimekeepingDomain} for testing.
*
* @author Michael Belivanakis (michael.gr)
*/
class FakeTimekeepingDomain implements TimekeepingDomain
{
private Instant currentInstant;
FakeTimekeepingDomain( Instant startTime )
{
currentInstant = startTime;
}
@Override
public Instant getCurrentInstant()
{
currentInstant = currentInstant.plusMillis( 1L );
return currentInstant;
}
@Override
public ZoneId getSystemZoneId()
{
return ZoneOffset.UTC;
}
@Override
public void sleepUntil( Instant instant )
{
if( instant.isAfter( currentInstant ) )
currentInstant = instant;
}
@Override
public void sleepFor( Duration duration )
{
if( !duration.isNegative() )
currentInstant = currentInstant.plus( duration );
}
}
实例化如下:
TimekeepingDomain timekeepingDomain =
new FakeTimekeepingDomain( Instant.parse( "2014-08-11T10:15:30.00Z" ) );
那么接下来会发生什么,你的 calculateSomethingRelativeToNow()
方法变成非静态的,包含它的 class 被传递一个 TimekeepingDomain
作为构造函数参数,这样你的方法就可以调用该接口以获取(您希望它认为的)当前时间。
在您的生产设备中,您将 class 传递给 TimekeepingDomain
的真实实现的引用,一切都像以前一样工作。
在你的测试装置中,你可以传递你的 class 一个 FakeTimekeepingDomain
的实例,它已经被实例化以假装 'current' 时间是某个固定时间,(在过去, 或未来,都没有关系,) 这样您就可以检查您的方法的结果,看看它实际上是预期的结果。您可以尝试各种固定时间,以检查各种结果。
本质上,这是手动完成的依赖注入,没有使用像 Spring
这样的重型框架来实现它。
您可以重构 class 如下:
public class DateUtils {
public static Date get31OfDecember2YearsAgo() {
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date()); // This line is surely redundant?
return get31OfDecember2YearsAgo(calendar);
}
static Date get31OfDecember2YearsAgo(Calendar calendar) {
int year2YearsAgo = calendar.get(Calendar.YEAR) - 2;
calendar.set(year2YearsAgo, Calendar.DECEMBER, 31);
return calendar.getTime();
}
}
这将允许您测试 package-private 变体并使用一些已知值来检查正确的结果。