5.5使用Cucumber來測試
這一節,我們將繼續前面的例子,然後不加Cucumber-JVM實現,這個將需要以java為基礎的最初Ruby Cucumber框架的版本,並且建立一些測試來說明我們的應用和觀點。
5.5.1程式碼實現
- 我們需要在build.gradle的檔案中加入如下的包依賴:
testCompile("info.cukes:cucumber-spring:1.2.2") |
- 接著,我們需要建立測試驅動去執行Cucumber測試類。讓我們建立一個RunCukeTest.java的檔案,放置到src/test/java/org/owen/bookpub目錄下。
@RunWith(Cucumber.class) @CucumberOptions(plugin = { "pretty", "html:build/reports/cucumber" }, glue = { "cucumber.api.spring", "classpath:org.test.bookpub" }, monochrome = public class RunCukeTests { } |
- 接下來,我們需要用到Cucumber涉及到的步驟宣告。我人先建立RepositoryStepefs.java的檔案放置到src/test/java/org/owen/bookpub目錄下。
@WebAppConfiguration @ContextConfiguration(classes = {BookPubApplication.class, TestMockBeansConfig.class }, loader = SpringApplicationContextLoader.class) public class RepositoryStepdefs { @Autowired private WebApplicationContext context; @Autowired private DataSource ds; @Autowired private BookRepository bookRepository; private Book loadedBook; @Given("^([^\\\"]*) fixture is loaded$") public void data_fixture_is_loaded(String fixtureName) throws Throwable { ResourceDatabasePopulator populator = new ResourceDatabasePopulator( context.getResource("classpath:/" + fixtureName + ".sql")); DatabasePopulatorUtils.execute(populator, ds); } @Given("^(\\d+) books available in the catalogue$") public void books_available_in_the_catalogue(int bookCount) throws Throwable { assertEquals(bookCount, bookRepository.count()); } @When("^searching for book by isbn ([\\d-]+)$") public void searching_for_book_by_isbn(String isbn) throws Throwable { loadedBook = bookRepository.findBookByIsbn(isbn); assertNotNull(loadedBook); assertEquals(isbn, loadedBook.getIsbn()); } @Then("^book title will be ([^\"]*)$") public void book_title_will_be(String bookTitle) throws Throwable { assertNotNull(loadedBook); assertEquals(bookTitle, loadedBook.getTitle()); } } |
- 現在,我們需要建立響應測試特徵的宣告檔案,檔名為repositories.feature在src/test/resource/org/owen/bookpub目錄下。
@txn |
- 最後,我們需要建立packt-books.sql檔案在src/test/resources目錄下。
INSERT INTO author (id, first_name, last_name) VALUES (5, |
- 執行gradle test。
- 執行好了之後,我們也可以獲取到Cucumber特殊的測試報告檔案,該檔案位於build/reports/tests/index.html.開啟這個html檔案,我們將會看到如下資訊:
- 選擇Scenario:Load one book,將會連線到詳細的頁面。
- Cucumber也會建立自己的報告,報告路徑在build/reports/cucumber/index.html
- 行為驅動測試不僅僅可以建立個別的情況,也可以宣告完整大綱,也就是定義個引數供全域性檔案中的這個名稱的使用。讓我們在src/test/resource/org/owen/bookpub目錄下建立restful.feature的檔案,檔案內容如下:
@txn |
- 我們還需要在src/test/java/org/owen/bookpub目錄下建立RestfulStepdefs.java檔案。
import java.io.IOException; import org.owen.bookpub.repository.BookRepository; import org.springframework.beans.factory.annotation.Autowired; 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.ResultActions; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import cucumber.api.java.Before; import cucumber.api.java.en.Given; import cucumber.api.java.en.Then; import cucumber.api.java.en.When; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @WebAppConfiguration @ContextConfiguration(classes = { BookPubApplication.class, TestMockBeansConfig.class }, loader = SpringApplicationContextLoader.class) public class RestfulStepdefs { @Autowired private WebApplicationContext context; @Autowired private BookRepository bookRepository; private MockMvc mockMvc; private ResultActions result; @Before public void setup() throws IOException { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); } @Given("^catalogue with books$") public void catalogue_with_books() { assertTrue(bookRepository.count() > 0); } @When("^requesting url ([^\"]*)$") public void requesting_url(String url) throws Exception { result = mockMvc.perform(get(url)); } @Then("^status code will be ([\\d]*)$") public void status_code_will_be(int code) throws Throwable { result.andExpect(status().is(code)); } @Then("^response content contains ([^\"]*)$") public void response_content_contains(String content) throws Throwable { result.andExpect(content().string(containsString(content))); } } |
- 最後,執行測試檔案
5.5.2程式碼說明
讓我們回顧一下Sep Definitions。因為Cucumber框架使用Gherkin塑造檔案為了去描述業務規則及可以被測試,這些描述都是基於英語的句子,句子需要轉換為執行的程式碼。這就是Step Definition的工作。每一步都是一個明確的情節並且需要匹配Step Definition類中的方法,然後被執行。這些匹配通過宣告的一系列表達的步驟註解,如@Given,@When或@Then。正則表示式是匹配一個組,Cucumber使用正則表示式可以精確定位到要執行的方法。
在RepositoryStepdefs中,我們可以看到如下的方法:
@Given("^([^\\\"]*) fixture is loaded$") |
這個@Given註釋包含一個正則表示式匹配repositories.feature檔案中的Given packt-books fixture is loaded這一行資訊,之後packt-books資訊就要作為fixtureName的引數。@When 和 @Then 的註解原理也是一樣的。總而言之,Cucumber框架就是匹配來自我們定義的feature的檔案,然後將英語的單詞作為類的執行方法的對應引數。
我們已經瞭解了Cucumber基礎知識,讓我們來學習一下如何通過配置來讓測試整合Spring Boot.
這些所有的工作都是來自於RunCukeTests類。這個類並沒有包含任何的測試類,但是有兩份個重要的註解:@RunWith(Cucumber.class)和@CucumberOptons。
- @RunWith(Cucumber.class):這個是JUnit註解,宣告JUnit執行需要使用Cucumber Feature檔案去執行測試。
- @CucumberOptions:這個提供額外的配置給Cucumber.
- plugin={"pretty”, “html:build/reports/cucumber”}:這個宣告是讓Cucumber產生html形式的報告,並放入到build/reports/cucumber目錄下。
- glue={“cucumber.api.spring”, “classpath:org.test.bookup”}:為上一人非常重要的配置,它告訴Cucumber哪個包需要載入和從哪載入測試類。cucumber.api.spring包需要宣告為了cucumber和spring包的整合,org.test.bookpub是我們Step Definition實現的類所在處。
- monochrome = true:這個是告訴Cucumber不需要帶有ANSI顏色列印輸出,因為我們整合了JUnit。
現在讓我們來看一下RepositoryStepdefs類。
- @WebAppConfiguration告訴Spring這個類需要WebApplicationContext初始化,在執行過得,它將會用於測試目的。
- @ContextConfiguration(classes = {BookPubApplication.class,
TestMockBeansConfig.class}, loader =
SpringApplicationContextLoader.class)宣告去使用BookPubApplication和TestMockBeansConfig類,並將其加入Spring應用的上下文中。當然,使用來自SpringApplicationContextoader類為了去載入程式測試驅動。
因為Cucumber-Spring整合並不知道關於Spring Boot,但是僅僅知道關於Spring,我們不能使用@SpringApplicationConfiguration註解。我們必須使用來自於Spring的適合註解來彌補這個缺陷。我們通過配置類和載入到@ContextConfiguration來解決問題。
一旦我們註解了這個配置,Spring和Spring Boot將會提供給我們方便的自動載入bean用於我們的Step Definition類中。
Cucumber測試可以固定一個新的Step Definition類的實現給每一次測試執行。這個方法命名全域性的,我們可以用這個方法在任何已宣告的Step Definition類中;這些類的操作是各自的,不會影響到其它的。在每一次測試時,由於一個新的實現被建立,宣告的類是有狀態的,並且依賴於內部變數去保持從一個斷言到另外一個斷言的瞬間切換。例如,@When 註解的方法,一個特殊的狀態將會放入;@Then註解,一系列的斷言將會獲取值。在我們的RepositoryStepdefs類的例子中,searching_for_book_by_isbn(…)方法中,我們通過查詢會等到一個變數loadedBook的類,這個類之後會給後面的book_will_be方法使用。在這樣的情況下,如果我們從混合規則中宣告不同的類在我們的feature檔案中,這些類之間是不能互相引用,即不相影響。
在我們整合了Spring後,我們可以解決不同的類之間不能互相呼叫的問題。我們可以看到PublisherRepositoryTests這個之間我們建立的測試類。這個類中有能力可以將@Given註解方法中的特殊行為的mock給其它的測試類,也就是說可以注入或例項到@Then方法註解的方法中。
另外我們聲明瞭一個RestfulStepdefs的類,這裡我們注入了BookRepository類。然而,在restful.feature檔案中,Given packt-books fixture is load的宣告將會轉換到ReposioryStepdefs類的data_fixture_is_loaded的方法中。但是這個兩個類會分享著相同的例項後BookRepositoty物件,並且插入packt-books.sql到資料庫中。
另外一個特徵,我們使用了@txn的註解。這個是告訴Spring在執行測試之間要去擦除,重置資料,隨後要清楚資料庫狀態。