Spring + Mybatis 單元測試 (DAO)

主要依賴

  • Spring : 4.3.12.RELEASE
  • mybatis : 3.4.5
  • junit : 4.12
  • hamcrest-all : 1.3
  • h2 : 1.4.196
  • DbSetup : 2.1.0

DAO 層的單元測試應該怎麼做

如果按照單元測試的定義,我們的不應該依賴於如數據庫、網絡、文件等,使用 Mockito 這類框架可以模擬這些外界因素。但是對於 DAO 層來說,脫離了數據庫,單元測試還能發揮它應有的作用嗎?

看下面使用 Mockito 的例子

@Test
public void testAdd() {
    Student student = new Student();
    StudentDao studentDao = mock(StudentDao.class);
    studentDao.add(student);
    verify(studentDao).add(student);
}

說實話,上面的測試沒有任何作用,即使這個測試通過,它甚至不能保證這個DAO 所執行的 SQL 能在數據庫中執行。DAO 層跟數據庫耦合的是如此的緊密,以至於脫離數據庫的測試沒有一點意義。其實我們對 DAO 層的測試是爲了兩點:

  • 它執行了正確的 SQL 嗎?
  • 它返回了我們所需要的數據嗎?

這兩點都是需要數據庫環境支持的。那麼我們需要連接本地的數據庫進行測試嗎?比如說連接本地的 MySQL ? 不,這樣會使得測試過於依賴運行的電腦環境,如果別的同事 clone 了這個項目,他還需要搭建測試用的數據庫,這很麻煩。所以我們使用內存數據庫,像我這邊是用 h2 數據庫,這樣我們只需添加依賴包即可。

如此看來這樣的測試已經不能叫單元測試了,更像是集成測試?

哪些是需要測試的?

在我看來,除了最基本的 CURD,其它都測。因爲 CURD 通常都是代碼生成器,像 mybatis-generator 這樣的工具生成的,測試這些生成的 SQL 就像測試第三方的類一樣意義不大。只需要測試自己寫的方法就好。

Spring Test

Spring 本身就有一套測試框架,很容易跟 Junit 整合。這裏的例子使用 Maven 作爲構建工具。

測試用到的依賴

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-all</artifactId>
    <version>1.3</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.196</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>com.ninja-squad</groupId>
    <artifactId>DbSetup</artifactId>
    <version>2.1.0</version>
    <scope>test</scope>
</dependency>

測試目錄結構

測試目錄結構

在 Spring 的測試配置文件 application-context.xml 中,使用 h2 數據庫作爲數據源

<jdbc:embedded-database id="dataSource" type="H2" generate-name="true"/>

//初始化數據庫
<jdbc:initialize-database>
    <jdbc:script location="schema.sql" encoding="UTF-8"/>
</jdbc:initialize-database>

這裏我們有一個初始化數據庫的文件 schema.sql ,用來建立使用到的表。本例使用的表有 student、clazz

create table student (
  id   int not null auto_increment primary key,
  name varchar(225),
  clazz_id int
);

create table clazz (
  id         int     not null auto_increment primary key,
  name       varchar(225),
  grade      tinyint not null
)

假設我們有一個 StudentDao 類,有一個方法 List<Student> findByGrade(Long grade) ,根據年級查找學生,我們建立相應的測試類

  • @RunWith(SpringJUnit4ClassRunner.class) 使用 Spring 提供的 runner。
  • @ContextConfiguration("classpath:application-context.xml") 指定 Spring 配置文件
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-context.xml")
public class StudentDaoTest {
    @Test
    public void testFindByGrade() {
    }
}

好了,現在我們有了一個內存數據庫,表也有了,那就還差數據了。當然我們可以在初始化數據庫表的時候將測試數據插入,但相對於每一個測試來說,數據不夠細,且這些數據不好維護。我更傾向於在測試類中初始化相應的數據。我一開始看了下 DbUnit,它是使用 XML 來構造數據的,說實話,XML 也不好維護。後來發現了 DbSetup,感覺好用一點,雖然他們的功能是不完全相同的,對於測試有對數據庫進行數據修改,如更新、插入、刪除等方法,DbUnit還是比較在行。DbSetup 主要是關注於數據的初始化。具體的用法這些就自己去看了,這裏給個簡單的例子

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:application-context.xml")
public class StudentDaoTest {
    private static DbSetupTracker dbSetupTracker = new DbSetupTracker();
    @Autowired DataSource dataSource;
    @Autowired StudentDao studentDao;

    @Before
    public void setUp() {
        Operation operation = Operations.sequenceOf(
                Operations.deleteAllFrom("student", "clazz"),
                Operations.insertInto("student")
                        .columns("id", "name", "clazz_id")
                        .values(1L, "Tom", 1L)
                        .values(2L, "Jack", 2L)
                        .values(3L, "Red", 1L)
                        .build(),
                Operations.insertInto("clazz")
                        .columns("id", "name", "grade")
                        .values(1L, "1-A", 1L)
                        .values(2L, "2-B", 2L)
                        .build()
        );
        DbSetup dbSetup = new DbSetup(new DataSourceDestination(dataSource), operation);
        dbSetupTracker.launchIfNecessary(dbSetup);
    }

    @Test
    public void testFindByGrade() {
        dbSetupTracker.skipNextLaunch();

        List<Student> actual = studentDao.findByGrade(1L);

        assertThat(actual, hasSize(2));
        assertThat(actual, hasItems(
                hasProperty("id", is(1L)),
                hasProperty("id", is(2L))
        ));
    }
}

setUp 方法中,即使沒有看過 DbSetup 的文檔,相信你也很容易就看懂它是如何初始化數據的。然後在 testFindByGrade 方法中,就是典型的斷言了。這裏我只是對 Student 的 id 進行了比較,當然你可以對比所有的屬性。

發佈了50 篇原創文章 · 獲贊 26 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章