BDD Java Testing with Spock

匿名 (未验证) 提交于 2019-12-02 20:59:24
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/itsoftchenfei/article/details/84984053

为何会专门写这篇BDD呢?之前我发表过一篇《代码重构之TDD的思考》,有童靴联系到我,探讨之余,感觉这几年集成化方面的测试方案产出太少了,优秀的也太少了。今天带大家了解一个新东西“BDD”,纠结是新瓶换旧药还是别的呢?

BDD(Behavior-Driven Development),也叫行为驱动开发,感觉是不是像TDD(Test-Driven Development)一样的空白无力。其实不然,它是在TDD的基础上发展而来的一种软件开发方法。

首先我们剖析一下TDD的问题有哪些:

  • 最大的弊端是面对一大堆的功能需求和用例时往往会感到无从下手
  • TDD更侧重于测试本身,因此容易忽视对业务需求的表达,最终沉溺于琐碎细节而无法自拔
  • 它没有相应的系统的解决方案(相当一部分人认为junit/testng+Jmock就行)

那BDD就能解决上面这些问题吗?个人认为,不完全能够,它只是提供了一直新的思路,应该说是一整套的解决方案(spock)。

战略视角看

传统的软件开发(总而言之就是各司其职,浓厚的部门墙)

BDD的本质在于尽可能避免在需求描述、用例撰写、代码实现、测试等各环节衔接、转译过程中发生的信息丢失。BDD以用例Use Case和示例Example为核心,借助Gherkin等一些特有概念和一系列BDD特有工具,以更贴近业务场景的方式,实现软件需求的完整实现。

战术层面

BDD的每个迭代可以按下面的方法来进行:

这里你会问Business Goal是个什么鬼?

采用Why-Who-How-What四步法发掘Business Goal
Why:出于何种原因要开发此系统?
Who:谁将从系统中受益?谁是系统的用户?谁会影响系统的开发?
How:怎样才能更便捷、更容易地达成业务目标?
What:系统能做哪些工作来实现业务目标?

发现Feature时,则采用了Why-Who-What-How四步法进行Feature的粒度细分,得到最终的用户故事。

  1. 规划整个系统,得到抽象的业务目标Business Goal
  2. 将业务目标细化为若干个具体的功能需求Feature
  3. 采取Given-When-Then三段式编写Gherkin,用具体的场景示例Example描述和演示特定的功能
  4. 将Example转换为可执行的Specification
  5. 将Specification再拆解为更低层次的、逼近代码实现的Low-Level Specification与测试Test
  6. 从Low-Level Specification中得到代码实现,并析取活动文档Living Documentation

对,它产出的也是测试代码,那和junit有啥区别啊?不急

与junit的区别

JUnit关注的则是更细碎的单元测试层面的东西。二者关注的角度、粒度和要解决的问题都不同。Spock能胜任从单元测试、集成测试到验收测试的所有工作。

怎样快速上手

这里稍微讲一下,在Spring Boot项目中使用Spock框架,其实也比较简单,它采用Gherkin语法形式(Given-When-Then),采用groovy语言来完成测试。编写一个测试用例主要会用到这些,

  • setup:这个块用于定义变量、准备测试数据、构建mock对象等;
  • expect:一般跟在setup块后使用,包含一些assert语句,检查在setup块中准备好的测试环境
  • when:在这个块中调用要测试的方法;
  • then : 一般跟在when后使用,尽可以包含断言语句、异常检查语句等等,用于检查要测试的方法执行后结果是否符合预期;
  • cleanup:用于清除setup块中对环境做的修改,即将当前测试用例中的修改回滚,在这个例子中我们对publisherRepository对象执行重置操作。

注意:spring-boot-maven-plugin这个插件同时也支持在Spring Boot框架中使用Groovy语言。

<dependency>



</dependency>
<dependency>



<dependency>



</dependency>

 /** * step1: * 在src/test目录下创建groovy文件夹,在groovy文件夹下创建com/test/bookpub包。 * 在resources目录下添加packt-books.sql文件,内容如下所示: * INSERT INTO author(id, first_name, last_name) VALUES(2, 'alex', 'chen'); * INSERT INTO book(isbn, title, author, publisher) VALUES('9527', 'Docker', 2, 1); * step2: * 在com/test/bookpub目录下创建SpockBookRepositorySpecification.groovy文件 */ @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 	} 	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) 	} } //接下来试验下Spock如何与mock对象一起工作 @Configuration @UsedForTesting public class TestMockBeansConfig {     @Bean     @Primary     public PublisherRepository createMockPublisherRepository() {         return Mockito.mock(PublisherRepository.class);     } } //测试"Test RESTful GET books by publisher" @Controller public class BookController{ 	@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(); 	} }

到这里基本上已经结束完了,最后一本必读书籍《Java Testing with Spock》 。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!