知識庫 / Spring RSS 訂閱

避免易碎測試,適用於服務層

Spring,Testing
HongKong
4
03:12 PM · Dec 06 ,2025

目錄

1. 概述

有許多方法可以 測試應用程序的服務層。 本文的目標是展示一種通過完全模擬與數據庫的交互來隔離測試該層的單元測試方法。

本示例將使用 Spring 進行依賴注入,JUnit、HamcrestMockito 用於測試,但技術可以有所不同。

2. 層級

典型的 Java Web 應用程序會在 DAL/DAO 層之上構建一個服務層,而該服務層又會調用底層的持久化層。

1.1. 服務層

@Service
public class FooService implements IFooService{

   @Autowired
   IFooDAO dao;

   @Override
   public Long create( Foo entity ){
      return this.dao.create( entity );
   }

}

1.2. DAL/DAO 層

@Repository
public class FooDAO extends HibernateDaoSupport implements IFooDAO{

   public Long create( Foo entity ){
      Preconditions.checkNotNull( entity );

      return (Long) this.getHibernateTemplate().save( entity );
   }

}

3. 動機與單元測試的邊界模糊化

當對服務進行單元測試時,通常的單元測試單位是服務 ,簡單來説。測試會模擬底層層級的交互——在本例中是 DAO/DAL 層,並驗證其交互。對於 DAO 層也完全一樣——模擬與數據庫的交互(例如,HibernateTemplate)並驗證這些交互。

這種方法是有效的,但會導致測試變得脆弱——添加或刪除任何一層級幾乎總是意味着完全重寫測試。這是因為測試依賴於層級的精確結構,而對該結構進行任何更改都意味着對測試進行更改。

為了避免這種缺乏靈活性,我們可以通過改變單元測試的定義來擴大測試範圍——將持久化操作作為單元進行測試,從服務層級一直到原始持久化層級——無論它是什麼。現在,單元測試將消費服務層的 API,並模擬原始持久化層級,例如 HibernateTemplate

public class FooServiceUnitTest{

   FooService instance;

   private HibernateTemplate hibernateTemplateMock;

   @Before
   public void before(){
      this.instance = new FooService();
      this.instance.dao = new FooDAO();
      this.hibernateTemplateMock = mock( HibernateTemplate.class );
      this.instance.dao.setHibernateTemplate( this.hibernateTemplateMock );
   }

   @Test
   public void whenCreateIsTriggered_thenNoException(){
      // When
      this.instance.create( new Foo( "testName" ) );
   }

   @Test( expected = NullPointerException.class )
   public void whenCreateIsTriggeredForNullEntity_thenException(){
      // When
      this.instance.create( null );
   }

   @Test
   public void whenCreateIsTriggered_thenEntityIsCreated(){
      // When
      Foo entity = new Foo( "testName" );
      this.instance.create( entity );

      // Then
      ArgumentCaptor< Foo > argument = ArgumentCaptor.forClass( Foo.class );
      verify( this.hibernateTemplateMock ).save( argument.capture() );
      assertThat( entity, is( argument.getValue() ) );
   }

}

現在測試僅關注單一職責——創建觸發時,創建操作是否成功到達數據庫?

上一次測試使用了Mockito驗證語法,以檢查save方法是否被調用於hibernate模板,並捕獲參數以便進行驗證。通過這種交互測試,驗證了創建實體的責任,無需檢查任何狀態——測試信任hibernate的保存邏輯能夠正常工作。當然,這本身也需要進行測試,但這屬於另一項責任,也是另一種類型的測試。

4. 結論

這種技術始終導致更集中的測試,使其更能適應變化並保持彈性。測試現在應該失敗的唯一原因是所測試的責任已中斷。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.