如果您的大多数服务只是简单地传递到您的 daos,并且您的 daos 只调用 Spring 上的方法,HibernateTemplate
那么JdbcTemplate
您是正确的,单元测试并不能真正证明您的集成测试已经证明的任何事情。然而,由于所有常见的原因,进行单元测试是有价值的。
由于单元测试只测试单个类,在没有磁盘或网络访问的内存中运行,并且从不真正测试多个类一起工作,它们通常是这样的:
- 服务单元测试模拟 daos。
- Dao 单元测试模拟数据库驱动程序(或 spring 模板)或使用嵌入式数据库(在 Spring 3 中超级简单)。
要对刚刚通过 dao 的服务进行单元测试,您可以像这样模拟:
@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 的实现。哎哟。
现在这个例子假设服务没有真正的业务逻辑。通常,这些服务除了 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 风格编写,那么无论如何你都会拥有它们。