1. 程式人生 > >在Spring Boot專案中使用Spock測試框架

在Spring Boot專案中使用Spock測試框架

本文首發於個人網站:在Spring Boot專案中使用Spock測試框架

Spock框架是基於Groovy語言的測試框架,Groovy與Java具備良好的互操作性,因此可以在Spring Boot專案中使用該框架寫優雅、高效以及DSL化的測試用例。Spock通過@RunWith註解與JUnit框架協同使用,另外,Spock也可以和Mockito(Spring Boot應用的測試——Mockito)一起使用。

在這個小節中我們會利用Spock、Mockito一起編寫一些測試用例(包括對Controller的測試和對Repository的測試),感受下Spock的使用。

實戰

  • 根據Building an Application with Spring Boot這篇文章的描述,spring-boot-maven-plugin
    這個外掛同時也支援在Spring Boot框架中使用Groovy語言。
  • 在pom檔案中新增Spock框架的依賴
<!-- test -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.spockframework</groupId>
   <artifactId>spock-core</artifactId>
   <scope>test</scope></dependency>
<dependency>
   <groupId>org.spockframework</groupId>
   <artifactId>spock-spring</artifactId>
   <scope>test</scope>
</dependency>
  • 在src/test目錄下建立groovy資料夾,在groovy資料夾下建立com/test/bookpub包。
  • 在resources目錄下新增packt-books.sql檔案,內容如下所示:
INSERT INTO author (id, first_name, last_name) VALUES (5, 'Shrikrishna', 'Holla');
INSERT INTO book (isbn, title, author, publisher) VALUES ('978-1-78398-478-7', 'Orchestrating Docker', 5, 1);
INSERT INTO author (id, first_name, last_name) VALUES (6, 'du', 'qi');
INSERT INTO book (isbn, title, author, publisher) VALUES ('978-1-78528-415-1', 'Spring Boot Recipes', 6, 1);
  • com/test/bookpub目錄下建立SpockBookRepositorySpecification.groovy檔案,內容是:
package com.test.bookpubimport com.test.bookpub.domain.Author

import com.test.bookpub.domain.Book
import com.test.bookpub.domain.Publisher
import com.test.bookpub.repository.BookRepository
import com.test.bookpub.repository.PublisherRepository
import org.mockito.Mockito
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.SpringApplicationContextLoader
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.web.WebAppConfiguration
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import spock.lang.Sharedimport spock.lang.Specification
import javax.sql.DataSourceimport javax.transaction.Transactional

import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebAppConfiguration
@ContextConfiguration(classes = [BookPubApplication.class,
 TestMockBeansConfig.class],loader = SpringApplicationContextLoader.class)
class SpockBookRepositorySpecification extends Specification {
    @Autowired
    private ConfigurableApplicationContext context;
    @Shared
    boolean sharedSetupDone = false;
    @Autowired
    private DataSource ds;
    @Autowired
    private BookRepository bookRepository;
    @Autowired
    private PublisherRepository publisherRepository;
    @Shared
    private MockMvc mockMvc;

    void setup() {
        if (!sharedSetupDone) {
            mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
            sharedSetupDone = true;
        }
        ResourceDatabasePopulator populator = new 
               ResourceDatabasePopulator(context.getResource("classpath:/packt-books.sql"));
        DatabasePopulatorUtils.execute(populator, ds);
    }

    @Transactional
    def "Test RESTful GET"() {
        when:
        def result = mockMvc.perform(get("/books/${isbn}"));
  
        then:
        result.andExpect(status().isOk()) 
       result.andExpect(content().string(containsString(title)));

       where:
       isbn              | title
      "978-1-78398-478-7"|"Orchestrating Docker"
      "978-1-78528-415-1"|"Spring Boot Recipes"
    }

    @Transactional
    def "Insert another book"() {
      setup:
      def existingBook = bookRepository.findBookByIsbn("978-1-78528-415-1")
      def newBook = new Book("978-1-12345-678-9", "Some Future Book",
              existingBook.getAuthor(), existingBook.getPublisher())

      expect:
      bookRepository.count() == 3

      when:
      def savedBook = bookRepository.save(newBook)

      then:
      bookRepository.count() == 4
      savedBook.id > -1
  }
}
  • 執行測試用例,測試通過
  • 接下來試驗下Spock如何與mock物件一起工作,之前的文章中我們已經在TestMockBeansConfig類中定義了PublisherRepository的Spring Bean,如下所示,由於@Primary的存在,使得在執行測試用例時Spring Boot優先使用Mockito框架模擬出的例項。
@Configuration
@UsedForTesting
public class TestMockBeansConfig {
    @Bean
    @Primary
    public PublisherRepository createMockPublisherRepository() {
        return Mockito.mock(PublisherRepository.class);
    }
}
  • 在BookController.java中新增getBooksByPublisher介面,程式碼如下所示:
@Autowired
public PublisherRepository publisherRepository;

@RequestMapping(value = "/publisher/{id}", method = RequestMethod.GET)
public List<Book> getBooksByPublisher(@PathVariable("id") Long id) {
    Publisher publisher = publisherRepository.findOne(id);
    Assert.notNull(publisher);
    return publisher.getBooks();
}
  • SpockBookRepositorySpecification.groovy檔案中新增對應的測試用例,
def "Test RESTful GET books by publisher"() {
    setup:
    Publisher publisher = new Publisher("Strange Books")
    publisher.setId(999)
    Book book = new Book("978-1-98765-432-1",
            "Mytery Book",
            new Author("Jhon", "Done"),
            publisher)
    publisher.setBooks([book])
    Mockito.when(publisherRepository.count()).
            thenReturn(1L);
    Mockito.when(publisherRepository.findOne(1L)).
            thenReturn(publisher)

    when:
    def result = mockMvc.perform(get("/books/publisher/1"))

    then:
    result.andExpect(status().isOk())
    result.andExpect(content().string(containsString("Strange Books")))

    cleanup:
    Mockito.reset(publisherRepository)
}
  • 執行測試用例,發現可以測試通過,在控制器將物件轉換成JSON字串裝入HTTP響應體時,依賴Jackson庫執行轉換,可能會有迴圈依賴的問題——在模型關係中,一本書依賴一個出版社,一個出版社有包含多本書,在執行轉換時,如果不進行特殊處理,就會迴圈解析。我們這裡通過@JsonBackReference註解阻止迴圈依賴。

分析

可以看出,通過Spock框架可以寫出優雅而強大的測試程式碼。

首先看SpockBookRepositorySpecification.groovy檔案,該類繼承自Specification類,告訴JUnit這個類是測試類。檢視Specification類的原始碼,可以發現它被@RunWith(Sputnik.class)註解修飾,這個註解是連線Spock與JUnit的橋樑。除了引導JUnit,Specification類還提供了很多測試方法和mocking支援。

Note:關於Spock的文件見這裡:Spock Framework Reference Documentation

根據《單元測試的藝術》一書中提到的,單元測試包括:準備測試資料、執行待測試方法、判斷執行結果三個步驟。Spock通過setup、expect、when和then等標籤將這些步驟放在一個測試用例中。

  • setup:這個塊用於定義變數、準備測試資料、構建mock物件等;
  • expect:一般跟在setup塊後使用,包含一些assert語句,檢查在setup塊中準備好的測試環境
  • when:在這個塊中呼叫要測試的方法;
  • then : 一般跟在when後使用,儘可以包含斷言語句、異常檢查語句等等,用於檢查要測試的方法執行後結果是否符合預期;
  • cleanup:用於清除setup塊中對環境做的修改,即將當前測試用例中的修改回滾,在這個例子中我們對publisherRepository物件執行重置操作。

Spock也提供了setup()和cleanup()方法,執行一些給所有測試用例使用的準備和清除動作,例如在這個例子中我們使用setup方法:(1)mock出web執行環境,可以接受http請求;(2)載入packt-books.sql檔案,匯入預定義的測試資料。web環境只需要Mock一次,因此使用sharedSetupDone這個標誌來控制。

通過@Transactional註解可以實現事務操作,如果某個方法被該註解修飾,則與之相關的setup()方法、cleanup()方法都被定義在一個事務內執行操作:要麼全部成功、要麼回滾到初始狀態。我們依靠這個方法保證資料庫的整潔,也避免了每次輸入相同的資料。

Spring Boot 1.x系列

  1. Spring Boot的自動配置、Command-line-Runner
  2. 瞭解Spring Boot的自動配置
  3. Spring Boot的@PropertySource註解在整合Redis中的使用
  4. Spring Boot專案中如何定製HTTP訊息轉換器
  5. Spring Boot整合Mongodb提供Restful介面
  6. Spring中bean的scope
  7. Spring Boot專案中使用事件派發器模式
  8. Spring Boot提供RESTful介面時的錯誤處理實踐
  9. Spring Boot實戰之定製自己的starter
  10. Spring Boot專案如何同時支援HTTP和HTTPS協議
  11. 自定義的Spring Boot starter如何設定自動配置註解
  12. Spring Boot專案中使用Mockito

本號專注於後端技術、JVM問題排查和優化、Java面試題、個人成長和自我管理等主題,為讀者提供一線開發者的工作和成長經驗,期待你能在這裡有所收穫。

相關推薦

Spring Boot專案使用Spock測試框架

本文首發於個人網站:在Spring Boot專案中使用Spock測試框架 Spock框架是基於Groovy語言的測試框架,Groovy與Java具備良好的互操作性,因此可以在Spring Boot專案中使用該框架寫優雅、高效以及DSL化的測試用例。Spock通過@RunWith註解與JUnit框架協同使用

Spring Boot 專案引入websocket後,執行Junit測試報錯

1、報錯資訊如下 java.lang.IllegalStateException: Failed to load ApplicationContext     at org.springframework.test.context.cache.Default

spring boot專案使用@Slf4j註解

1、在pom.xml中引入lombok的依賴 <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </depe

Spring Boot專案@Value取不到配置檔案的配置引數值

Spring Boot專案中@Value取不到配置檔案中的配置引數值 原取值方式如下所示: @Value("${authox.sql.url}") private static String url; @Value("${authox.sql.username}") private stati

spring boot 專案 獲取ApplicaitonContext

給大家推薦個靠譜的公眾號程式設計師探索之路,大家一起加油 ApplicationContextAware 這個介面物件就是我們今天的主角,其實以實現ApplicationContextAware介面的方式獲取ApplicationContext物件例項並不是SpringBoot特有的功能

spring boot 專案hanlp的配置(可增加自定義詞典)

pom.xml檔案中增加: com.hankcs hanlp system ${project.basedir}/src/main/resources/lib/hanlp-1.5.2.jar 字典和模型檔案在專案中的位置,其中包括自定義詞典: data資料夾在專案中的位置:

spring boot專案使用 RedisTemplate/StringRedisTemplate 學習經歷

專案開發時遇到需要去防止兩個服務同時跑一個功能時而導致公用的一個表出現資料錯誤的問題,領導說了幾個解決方案redis是唯一一個聽過的就趕緊學了一下,因為並未去很好的瀏覽專案結構剛開始繞了很大一圈,自己建立連線池配置檔案引pom啥的,結果發現都是已經搭好的,但也對redis有了更深的認識,先貼下程式碼 app

三分鐘學會在spring boot 專案使用RabbitMq做訊息佇列

第一步:在spring boot專案中新增RabbitMq的maven依賴 <dependency> <groupId>org.springframework.boot</groupId>

spring boot 專案使用thymeleaf模板,將後臺資料傳遞給前臺介面。

1、將後臺資料傳遞給前臺有很多種方式,可以將後臺要傳遞的資料轉換成json格式,去傳遞給前臺,也可以通過model形式去傳遞出去,這篇部落格主要是使用thymeleaf模板,將後臺資料傳遞給前臺。 2、首先要在spring boot 專案中新增如下依賴:

spring boot專案 mybatis-config.xml的配置

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.o

spring boot 專案元件和各種註解

spring boot 專案分析 最近一直在做專案,都是有一塊補一塊,也沒系統的整理下spring boot個元件的功能和使用方式,就大致總結下,也是為了方便以後快速構建專案 (1) web 容器 spring-boot-starter-web 預設tomcat 可以去除tomcat (2)

技術文章 | spring boot專案使用jpa的一個未解之謎

 本文來源於阿里雲-雲棲社群,原文點選這裡。 公司最近主要的工作就是把之前的一個專案進行幾乎全面的重構,之所以說幾乎全面,是因為除開業務邏輯外全部換血: 框架由spring+struts2+mybatis改為spring boot+jpa資料庫由sybase+h2改

spring boot專案加入 JWT

將JWT放到專案中作為一個引數被掉用發起者傳過來作為介面呼叫的口令 <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --> <dependency> <groupId>com

【JAVA-OSS】如何在spring-boot專案使用oss上傳下載檔案

準備工作: 1.申請oss 你會得到 endpoint,accessKeyId,bucketName,accessKeySecret 2.引入oss maven dependency : com.aliyun.oss:aliyun-sdk-oss:jar:2.5.0 3.

spring boot專案處理Schedule定時任務

預設,springboot已經支援了定時任務Schedule模組,所以一般情況已經完全能夠滿足我們的實際需求,一般來說,沒有必要在加入其他類似於:quartz另外,在這裡提一個實際專案中,關於定時任務的架構上的一些考慮:一般來說,實際專案中,為了提高服務的響應能力,我們一般會通過負載均衡的方式,或者反向代理多

spring boot專案使用spring-boot-devtools模組進行程式碼熱部署,避免重新啟動web專案

devtools模組,是為開發者服務的一個模組。主要的功能就是程式碼修改後一般在5秒之內就會自動重新載入至伺服器,相當於restart成功。 spring-boot提供的重新啟動技術使用兩個類載入器,一個類載入器用來載入那些不變的類(如第三方jar包提供的類),另外一個用來

spring-boot專案的單元測試

參考文章 正文 引入maven依賴 <dependency> <groupId>org.springframework.boot</groupId> <

spring boot專案應用swagger2

1.       在pom.xml中新增<dependency> <groupId>io.springfox</groupId> <artifactId&g

新建一個maven spring boot專案遇到的問題

package config; import com.alibaba.druid.pool.DruidDataSource; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.session.SqlSessionFactory; import

spring boot專案 mybatis-config.xml 的配置

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.o