如果您是經驗豐富的單元測試人員,那么當您看到任何與時間 , 并發性 , 隨機性 , 持久性和磁盤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