如果您是经验丰富的单元测试人员,那么当您看到任何与时间 , 并发性 , 随机性 , 持久性和磁盘I / O协同工作的代码时,您就会学会做笔记。
原因是测试可能非常脆弱,有时完全无法正确测试。 这篇文章将展示如何通过向消费者注入替代品来提取“时间”。 这篇文章将使用Spring 3作为Dependency Injection容器,尽管Guice ,其他DI容器或POJO上的构造器/设置器也可以使用。 我也将忽略Locales,因为重点在于DateTime的注入,而不是DateTime本身。
现有代码
您已经获得了一段代码进行单元测试(或者您正在创建一个代码,这是您的第一次尝试)。 我们的第一段代码,只有一个类:(该类是Spring 3.1控制器,其目的是作为String返回当前时间)
@Controller
@RequestMapping(value = '/time')
@VisibleForTesting
class TimeController {@RequestMapping(value = '/current', method = RequestMethod.GET)@ResponseBodypublic String showCurrentTime() {// BAD!!! Can't testDateTime dateTime = new DateTime();return DateTimeFormat.forPattern('hh:mm').print(dateTime);}
}
请注意,该类在该类中执行“新的DateTime()”。 这是相应的测试类:
我们进行测试会怎样? 假设我们有一台非常慢的机器怎么样。 您可能(并且很可能会)以比较DateTime与返回的DateTime不同 。 这是个问题!
首先要做的是删除依赖关系,但是我们该怎么做呢? 如果我们将DateTime设置为类的一个字段,我们仍然会遇到同样的问题。 介绍Google Guava的供应商界面。
Google Guava供应商
Supplier接口只有一个方法“ get()”,它将返回设置了任何供应商的实例。 例如,如果用户已登录,则供应商将返回用户的名字,如果尚未登录,则返回默认用户名:
public class FirstNameSupplier implements Supplier<String> {private String value;private static final String DEFAULT_NAME = 'GUEST';public FirstNameSupplier() {// Just believe that this goes and gets a User from somewhereString firstName = UserUtilities.getUser().getFirstName();// more Guavaif(isNullOrEmpty(firstName)) {value = DEFAULT_NAME;} else {value = firstName;}}@Overridepublic String get() {return value;}
}
对于您的实现方法,您并不在乎名字是什么,而只是得到一个名字。
重构DateTime
让我们继续。 有关使用Supplier的更真实的示例(以及本文的要点),让我们实现一个DateTime供应商,以将当前的DateTime还给我们。 在此过程中,我们还创建一个接口,以便我们可以创建模拟实现以进行测试:
public interface DateTimeSupplier extends Supplier<DateTime> {DateTime get();
}
和一个实现:
public class DateTimeUTCSupplier implements DateTimeSupplier {@Overridepublic DateTime get() {return new DateTime(DateTimeZone.UTC);}
}
现在,我们可以使用DateTimeUTCSupplier并将其注入需要当前DateTime作为DateTimeSupplier接口的代码中:
@Controller
@RequestMapping(value = '/time')
@VisibleForTesting
class TimeController {@Autowired@VisibleForTesting// Injected DateTimeSupplierDateTimeSupplier dateTime;@RequestMapping(value = '/current', method = RequestMethod.GET)@ResponseBodypublic String showCurrentTime() {return DateTimeFormat.forPattern('hh:mm').print(dateTime.get());}
}
为了测试这一点,我们需要创建一个MockDateTimeSupplier并有一个控制器传递要返回的特定实例:
public class MockDateTimeSupplier implements DateTimeSupplier {private final DateTime mockedDateTime;public MockDateTimeSupplier(DateTime mockedDateTime) {this.mockedDateTime = mockedDateTime;}@Overridepublic DateTime get() {return mockedDateTime;}
}
请注意,要保存的对象是通过构造函数传递的。 这不会使您获得当前的日期/时间,但是会返回您想要的特定实例
最后是我们的测试(略微)行使了我们上面实现的TimeController的测试:
public class TimeControllerTest {private final int HOUR_OF_DAY = 12;private final int MINUTE_OF_DAY = 30;@Testpublic void testShowCurrentTime() throws Exception {TimeController controller = new TimeController();// Create the mock DateTimeSupplier with our known DateTimecontroller.dateTime = new MockDateTimeSupplier(new DateTime(2012, 1, 1, HOUR_OF_DAY, MINUTE_OF_DAY, 0, 0));// Call our methodString dateTimeString = controller.showCurrentTime();// Using hamcrest for easier to read assertions and condition matchersassertThat(dateTimeString, is(String.format('%d:%d', HOUR_OF_DAY, MINUTE_OF_DAY)));}}
结论
这篇文章展示了如何使用Google Guava的Supplier接口抽象出DateTime对象,以便您在考虑单元测试的情况下更好地设计实现! 供应商是解决“只给我一些东西”的好方法,请注意,这是已知的某种东西。
祝您编程愉快,别忘了分享!
参考:在Mike的网站博客上,从我们的JCG合作伙伴 Mike 嘲笑JodaTime的DateTime和Google Guava的供应商 。
翻译自: https://www.javacodegeeks.com/2012/10/mocking-with-jodatimes-datetime-and.html