这一节,我们将继续前面的例子,然后不加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 = true) 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在执行测试之间要去擦除,重置数据,随后要清楚数据库状态。