测试双重图案
前段時間,我寫了一篇有關使用Test Double的后果的文章,但是與Test Double Patterns無關,僅是一個簡單的清單。 今天,我想對其進行更改,并解釋這些模式之間的差異。
正如我在提到的文章中寫道:
Test Double是允許我們控制被測單元之間依賴性的模式。 為了能夠在我們想要或/和/或驗證是否發生想要的行為時提供想要的行為。
因此,現在讓您想起了基礎知識時,我們可以轉到有趣的部分–讓我們看一下“測試雙重模式”。
虛擬對象
虛擬是TD(測試雙精度),當我們想要傳遞對象以填充參數列表時使用。 從未實際使用過。 這就是為什么它不總是被視為TD之一的原因-它不提供任何行為。
假設我們有發送報告的Sender類。 由于某些要求,我們需要將其包裝到另一個類中以提供有效的接口。 我們的課看起來像這樣:
public class ReportProcessor implements Processor {private Sender sender;public ReportProcessor(Sender sender) {this.sender = sender;}@Overridepublic void process(Report report) {sender.send(report);} }現在,我們的測試是什么樣的? 我們需要驗證什么? 我們必須檢查報告是否傳遞給Sender實例的send()方法。 可以按照以下步驟完成:
public class DummyTest {@Testpublic void shouldSentReportWhileProcessing() {Sender sender = aMessageSender();ReportProcessor reportProcessor = aReportProcessor(sender);Report dummyReport = new Report();reportProcessor.process(dummyReport);then(sender).should().send(dummyReport);}private ReportProcessor aReportProcessor(Sender sender) {return new ReportProcessor(sender);}private Sender aMessageSender() {return spy(Sender.class);} }如您所見,與我們的虛擬對象沒有任何交互。 僅創建報告并將其作為參數傳遞。 沒有行為,只有存在。
假物件
Fake Object只是測試類所依賴的對象的一種更簡單,更輕量的實現。 它提供了預期的功能。
在決定時要記住的重要事項是使其盡可能簡單。 任何其他邏輯都可能對測試的脆弱性和準確性產生重大影響。
假設我們有一個帶create()方法的ReportService,它的責任是僅在尚未創建Report的情況下創建一個Report。 為簡單起見,我們可以假設標題標識一個對象–我們不能有兩個標題相同的報表:
public class ReportService {private ReportRepository reportRepository;public ReportService(ReportRepository reportRepository) {this.reportRepository = reportRepository;}public void create(Title title, Content content) {if (!reportRepository.existsWithTitle(title)) {Report report = new Report(title, content);reportRepository.add(report);}} }我們可以通過多種方式測試此代碼,但我們將決定使用Fake Object:
class FakeReportRepository implements ReportRepository {private Map<Title, Report> reports = new HashMap<>();@Overridepublic void add(Report report) {reports.put(report.title(), report);}@Overridepublic boolean existsWithTitle(Title title) {return reports.containsKey(title);}@Overridepublic int countAll() {return reports.size();}@Overridepublic Report findByTitle(Title title) {return reports.get(title);} }我們的測試將如下所示:
public class FakeTest {@Testpublic void shouldNotCreateTheSameReportTwice() {FakeReportRepository reportRepository = new FakeReportRepository();ReportService reportService = aReportService(reportRepository);reportService.create(DUMMY_TITLE, DUMMY_CONTENT);reportService.create(DUMMY_TITLE, DUMMY_CONTENT);Report createdReport = reportRepository.findByTitle(DUMMY_TITLE);assertThat(createdReport.title()).isSameAs(DUMMY_TITLE);assertThat(createdReport.content()).isSameAs(DUMMY_CONTENT);assertThat(reportRepository.countAll()).isEqualTo(1);}private ReportService aReportService(ReportRepository reportRepository) {return new ReportService(reportRepository);} }存根對象
我們在對方法輸出感興趣的情況下使用Stub Object,以確保每次調用它時結果都將完全符合我們的期望。
通常,我們不會在測試中檢查是否調用了Stub,因為我們會通過其他斷言知道它。
在此示例中,我們將查看一個ReportFactory,該工廠將創建具有創建日期的報表。 為了可測試性,我們使用了依賴注入來注入DateProvider:
public class ReportFactory {private DateProvider dateProvider;public ReportFactory(DateProvider dateProvider) {this.dateProvider = dateProvider;}public Report crete(Title title, Content content) {return new Report(title, content, dateProvider.date());} }它允許我們在測試中使用Stub:
public class StubTest {@Testpublic void shouldCreateReportWithCreationDate() {Date dummyTodayDate = new Date();DateProvider dateProvider = mock(DateProvider.class);stub(dateProvider.date()).toReturn(dummyTodayDate);ReportFactory reportFactory = new ReportFactory(dateProvider);Report report = reportFactory.crete(DUMMY_TITLE, DUMMY_CONTENT);assertThat(report.creationDate()).isSameAs(dummyTodayDate);} }如您所見,我們僅對調用Stub的結果感興趣。
間諜對象
與Stub對象相反,當我們對間諜方法的輸入感興趣時,我們將使用Spies。 我們正在檢查它是否被調用。 我們可以檢查它被調用了多少次。
我們也可以將實際的應用程序對象用作間諜。 無需創建任何其他類。
讓我們從第一段回到ReportProcessor:
public class ReportProcessor implements Processor {// code@Overridepublic void process(Report report) {sender.send(report);} }可能您已經注意到我們在那里使用了Spy,但讓我們再次看一下測試:
public class SpyTest {@Testpublic void shouldSentReportWhileProcessing() {Sender sender = spy(Sender.class);ReportProcessor reportProcessor = aReportProcessor(sender);reportProcessor.process(DUMMY_REPORT);then(sender).should().send(DUMMY_REPORT);}private ReportProcessor aReportProcessor(Sender sender) {return new ReportProcessor(sender);} }我們要檢查對象是否以正確的方式包裝,并將參數傳遞給其(包裝的對象)方法調用。 這就是為什么我們在這里使用Spy的原因。
模擬對象
模擬對象通常被描述為Stub和Spy的組合。 我們指定期望接收的輸入,并在此基礎上返回正確的結果。
如果這是我們期望的,則調用模擬對象也可能導致拋出異常。
好的,讓我們再次看一下ReportService:
public class ReportService {//codepublic void create(Title title, Content content) {if (!reportRepository.existsWithTitle(title)) {Report report = new Report(title, content);reportRepository.add(report);}} }現在,我們將使用模擬對象代替偽對象:
@RunWith(MockitoJUnitRunner.class) public class MockTest {@Mock private ReportRepository reportRepository;@InjectMocks private ReportService reportService;@Testpublic void shouldCreateReportIfDoesNotExist() {given(reportRepository.existsWithTitle(DUMMY_TITLE)).willReturn(false);reportService.create(DUMMY_TITLE, DUMMY_CONTENT);then(reportRepository).should().add(anyReport());}@Testpublic void shouldNotCreateReportIfDoesNotExist() {given(reportRepository.existsWithTitle(DUMMY_TITLE)).willReturn(true);reportService.create(DUMMY_TITLE, DUMMY_CONTENT);then(reportRepository).should(never()).add(anyReport());}private Report anyReport() {return any(Report.class);} }為了澄清一切,我們的模擬對象是ReportRepository.existsWithTitle()方法。 如您所見,在第一個測試中,我們說如果調用帶有DUMMY_OBJECT參數的方法,它將返回true。 在第二個測試中,我們檢查相反的情況。
我們在兩個測試中的最后一個斷言(then()。should())是另一個TD模式。 你能認出哪一個嗎?
最后一句話
這就是我今天要說的有關測試雙重模式的全部內容。 我鼓勵您有意使用它們,不要盲目遵循在可能的情況下添加@Mock注釋的習慣。
我還邀請您閱讀有關使用Test Double的后果的文章,以了解使用TD模式時可能遇到的問題以及如何識別和解決此類問題。
如果您想進一步加深對這些模式的了解,那么會有一個很棒的頁面可以幫助您做到這一點: xUnit模式:Test Double 。
祝您測試順利! 使它們可讀且有價值。
如果您對“測試雙模式”有任何想法或疑問,請在評論中分享。
翻譯自: https://www.javacodegeeks.com/2015/09/test-double-patterns.html
總結
- 上一篇: 在Log4j2中更好地执行非日志记录器调
- 下一篇: 不喜欢节流吗?