使用没有DependencyInjection的Calendar.getInstance()测试自定义DateUtilClass

时间:2015-02-12 15:47:10

标签: java testing junit calendar mocking

考虑以下课程:

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,我可以在测试中注入并模拟它,但因为我没有依赖注入可用...

2 个答案:

答案 0 :(得分:1)

此代码使用CalendarDate进行了硬编码,因此您无法按原样对其进行测试。然后,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变体并使用一些已知值来检查正确的结果。