Spring的HibernateTemplate
或JdbcTemplate
如果你的大多數服務只是簡單地通過給DAO,和你的DAO做一點,但調用方法,那麼你是正確的,單元測試真的不證明什麼,你的集成測試已經證明。但是,進行單元測試對於所有常見原因都很有價值。
由於單元測試只測試一個類,在內存中沒有磁盤或網絡訪問運行,並且從來沒有真正測試多個類一起工作,他們通常是這樣的:
- 服務單元測試嘲笑DAOS。
- Dao單元測試模擬數據庫驅動程序(或彈簧模板)或使用嵌入式數據庫(在Spring 3中超級簡單)。
進行單元測試,僅僅通過對道的服務,你可以嘲笑像這樣:
@Before
public void setUp() {
service = new EventServiceImpl();
dao = mock(EventDao.class);
service.EventDao = dao;
}
@Test
public void creationDelegatesToDao() {
service.createEvent(sampleEvent);
verify(dao).createEvent(sampleEvent);
}
@Test(expected=EventExistsException.class)
public void creationPropagatesExistExceptions() {
doThrow(new EventExistsException()).when(dao).createEvent(sampleEvent);
service.createEvent(sampleEvent);
}
@Test
public void updatesDelegateToDao() {
service.updateEvent(sampleEvent);
verify(dao).updateEvent(sampleEvent);
}
@Test
public void findingDelgatesToDao() {
when(dao.findEventById(7)).thenReturn(sampleEvent);
assertThat(service.findEventById(7), equalTo(sampleEvent));
service.findEvents("Alice", 1, 5);
verify(dao).findEventsByName("Alice", 1, 5);
service.findEvents(null, 10, 50);
verify(dao).findAllEvents(10, 50);
}
@Test
public void deletionDelegatesToDao() {
service.deleteEvent(sampleEvent);
verify(dao).deleteEvent(sampleEvent);
}
但是,這真是一個好主意?這些Mockito斷言聲稱dao方法被調用,而不是它做到了預期!你會得到你的覆蓋範圍數字,但你或多或少地將你的測試綁定到dao的實現。哎喲。
現在這個例子假定該服務沒有真正的業務邏輯。通常情況下,這些服務將具有業務邏輯,而不是道路調用,並且您必須測試這些。
現在,對於單元測試daos,我喜歡使用嵌入式數據庫。
private EmbeddedDatabase database;
private EventDaoJdbcImpl eventDao = new EventDaoJdbcImpl();
@Before
public void setUp() {
database = new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("schema.sql")
.addScript("init.sql")
.build();
eventDao.jdbcTemplate = new JdbcTemplate(database);
}
@Test
public void creatingIncrementsSize() {
Event e = new Event(9, "Company Softball Game");
int initialCount = eventDao.findNumberOfEvents();
eventDao.createEvent(e);
assertThat(eventDao.findNumberOfEvents(), is(initialCount + 1));
}
@Test
public void deletingDecrementsSize() {
Event e = new Event(1, "Poker Night");
int initialCount = eventDao.findNumberOfEvents();
eventDao.deleteEvent(e);
assertThat(eventDao.findNumberOfEvents(), is(initialCount - 1));
}
@Test
public void createdEventCanBeFound() {
eventDao.createEvent(new Event(9, "Company Softball Game"));
Event e = eventDao.findEventById(9);
assertThat(e.getId(), is(9));
assertThat(e.getName(), is("Company Softball Game"));
}
@Test
public void updatesToCreatedEventCanBeRead() {
eventDao.createEvent(new Event(9, "Company Softball Game"));
Event e = eventDao.findEventById(9);
e.setName("Cricket Game");
eventDao.updateEvent(e);
e = eventDao.findEventById(9);
assertThat(e.getId(), is(9));
assertThat(e.getName(), is("Cricket Game"));
}
@Test(expected=EventExistsException.class)
public void creatingDuplicateEventThrowsException() {
eventDao.createEvent(new Event(1, "Id1WasAlreadyUsed"));
}
@Test(expected=NoSuchEventException.class)
public void updatingNonExistentEventThrowsException() {
eventDao.updateEvent(new Event(1000, "Unknown"));
}
@Test(expected=NoSuchEventException.class)
public void deletingNonExistentEventThrowsException() {
eventDao.deleteEvent(new Event(1000, "Unknown"));
}
@Test(expected=NoSuchEventException.class)
public void findingNonExistentEventThrowsException() {
eventDao.findEventById(1000);
}
@Test
public void countOfInitialDataSetIsAsExpected() {
assertThat(eventDao.findNumberOfEvents(), is(8));
}
即使大多數人可能稱之爲集成測試,我仍然稱這爲單元測試。嵌入式數據庫駐留在內存中,並且在測試運行時將其啓動並取消。但這依賴於嵌入式數據庫看起來與生產數據庫相同的事實。情況會是這樣嗎?如果不是,那麼所有這些工作都是無用的。如果是這樣,那麼,正如你所說,這些測試正在做與集成測試不同的任何事情。但我可以在mvn test
的需求下運行它們,我有信心重構。
因此,我編寫這些單元測試,並滿足我的覆蓋目標。當我編寫集成測試時,我斷言HTTP請求會返回預期的HTTP響應。是的,它包含了單元測試,但是,嘿,當你練習TDD時,無論如何你都會在實際的dao實現之前寫入單元測試。
如果你在你的dao之後編寫單元測試,那麼他們當然沒有寫作的樂趣。 TDD文獻充滿了關於如何在代碼之後編寫測試感覺就像工作並且沒有人想要這樣做的警告。
TL; DR:您的集成測試將包含您的單元測試,從這個意義上講,單元測試不會增加真正的測試值。但是,如果您有高覆蓋率的單元測試套件,則有信心進行重構。但是,當然如果dao簡單地調用Spring的數據訪問模板,那麼你可能不會重構。但你永遠不知道。最後,儘管如果單元測試首先以TDD風格編寫,無論如何你都會擁有它們。
雷,非常感謝你的詳細解釋和例子!我想我們會堅持先寫集成測試的方法。我甚至會說,Web應用程序的CRUD服務的單元測試是很好的,但真正重要的是集成測試。爲什麼不採用TDD方式,而是在實現服務之前編寫集成測試而不是單元測試? – fischermatte
這樣會好的;單元測試警察不會敲你的門。誠然,集成測試將會覆蓋單元測試。就我個人而言,我確實喜歡在那裏進行單元測試的感覺(對重構有信心,完整感和所有這些),但那只是我自己。如果_you_對通過集成測試獲得覆蓋率而非單元測試感到滿意,並且您的集成測試正在快速運行開發人員生成的集成測試,而不是QA生成的功能系統集成 - 接受生產測試,那麼很酷。 :) –