2012-09-17 108 views
6

在我們最近的項目Sonar抱怨測試覆蓋率較低。我們注意到它沒有考慮默認的集成測試。除了您可以配置Sonar,所以它會考慮它們(JaCoCo插件),我們正在討論我們團隊中的問題是否確實需要編寫單元測試,當您使用集成測試覆蓋所有服務和數據庫層時無論如何。當我已經完成集成測試時,我應該爲CRUD操作編寫單元測試嗎?

我對集成測試的意思是,我們所有的測試都是針對我們在生產中使用的相同類型的專用Oracle實例運行的。我們不嘲笑任何東西。如果服務取決於另一項服務,我們使用真實服務。我們在運行測試之前需要的數據,我們通過一些使用我們的服務/存儲庫(DAO)的工廠類來構建。

因此,從我的角度來看 - 爲簡單的CRUD操作編寫集成測試,特別是在使用Spring Data/Hibernate等框架時不是一項很大的工作。有時甚至更容易,因爲你不會想到什麼和如何模擬。

那麼,爲什麼我應該爲我的CRUD操作編寫單元測試,因爲我可以編寫的集成測試不太可靠?

我看到的唯一一點是集成測試需要更多的時間來運行,項目越大。所以你不想在辦理登機手續前將它們全部運行。但我不太確定這是否糟糕,如果你有一個與Jenkins/Hudson一起工作的CI環境。

所以 - 任何意見或建議,高度讚賞!

回答

10

Spring的HibernateTemplateJdbcTemplate如果你的大多數服務只是簡單地通過給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風格編寫,無論如何你都會擁有它們。

+0

雷,非常感謝你的詳細解釋和例子!我想我們會堅持先寫集成測試的方法。我甚至會說,Web應用程序的CRUD服務的單元測試是很好的,但真正重要的是集成測試。爲什麼不採用TDD方式,而是在實現服務之前編寫集成測試而不是單元測試? – fischermatte

+0

這樣會好的;單元測試警察不會敲你的門。誠然,集成測試將會覆蓋單元測試。就我個人而言,我確實喜歡在那裏進行單元測試的感覺(對重構有信心,完整感和所有這些),但那只是我自己。如果_you_對通過集成測試獲得覆蓋率而非單元測試感到滿意,並且您的集成測試正在快速運行開發人員生成的集成測試,而不是QA生成的功能系統集成 - 接受生產測試,那麼很酷。 :) –

1

如果您計劃將圖層暴露給項目外的其他組件,則您只需單獨測試每個圖層。對於Web應用程序來說,可以調用存儲庫層的唯一方式是由服務層完成,並且服務層可以調用的唯一方式是控制器層。所以測試可以在控制器層開始和結束。對於後臺任務,這些都在服務層調用,所以需要在這裏進行測試。

現在使用真實數據庫進行測試的速度相當快,所以如果您設計好安裝/拆卸過程,不會太慢地減慢測試速度。但是,如果有其他依賴關係可能緩慢或存在問題,那麼應該對它們進行嘲弄/扼殺。

這種方法會給你:

  • 良好的覆蓋
  • 實學測試
  • 最小的努力量
  • 最低refectoring努力

然而,在隔離測試層的量確實可以讓你的團隊更多地同時工作,所以一個開發人員可以做存儲庫,另一個可以做服務r一項功能,並生成獨立測試的工作。

當硒/功能測試合併時,總是會有雙倍的覆蓋率,因爲您無法單獨依靠這些測試,因爲它們運行速度太慢。但是,功能測試不一定需要覆蓋所有的代碼,只要核心功能已經被單元/集成測試所涵蓋,核心功能本身就足夠了。

相關問題