考虑以下课程:
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,我可以在测试中注入并模拟它,但因为我没有依赖注入可用...
答案 0 :(得分:1)
此代码使用Calendar
和Date
进行了硬编码,因此您无法按原样对其进行测试。然后,public static
方法通常不打算进行测试;相反,它们应该是如此简单,你可以只看它们并确定它们是否正确。
我强烈建议您抛弃此get31ofDecember2YearsAgo()
方法以及整个DateUtils
类,并重构整个系统,以便开始使用正确设计的可实例化的类来设置日期和时间。这样做时,请使用最新的java 8时间类,或者,如果你不幸使用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;
}
A&#34;真实&#34;计时域实施:
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 );
}
};
A&#34;假&#34;计时域名实施:
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()
方法变为非静态方法,并且包含它的类传递TimekeepingDomain
作为构造函数参数,以便您的方法可以调用该接口以便得到(你想要的是什么)当前的时间。
在您的制作工具中,您会向您的班级传递对TimekeepingDomain
的实际实现的引用,并且一切都像以前一样。
在您的测试装备中,您可以向您的班级传递FakeTimekeepingDomain
的实例,该实例已被实例化以假装当前的&#39;时间是一些固定的时间,(在过去或未来,无关紧要),以便您可以检查方法的结果,看看它实际上是预期的结果。您可以尝试各种固定时间,以检查各种结果。
本质上,这是手动完成依赖注入,而不使用像Spring
这样的重型框架来实现它。
答案 1 :(得分:-1)
您可以按如下方式重构该类:
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变体并使用一些已知值来检查正确的结果。