打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
BDD Testing with Cucumber, Java and JUnit

Whether behaviour-driven-development, specification by example or acceptance test driven development is the goal, the Cucumber framework eases our life when we need to  establish a link between the non-technical, textual description for a new feature and the tests that prove that the application fulfils these requirements.

In the following short tutorial I’d like to demonstrate how to add Cucumber to a Java project and how to write feature descriptions and test-cases for each step of these descriptions.

Running a Cucumber Test with jUnit in Eclipse IDE

Contents

  1. Dependencies
  2. A simplified Workflow (Suggestion)
  3. Example 1: Book Search
    1. Writing the Feature
    2. Domain Objects
      1. Book
      2. Library
    3. Adding a Test Runner for jUnit
    4. Adding Steps
  4. Example 2: Salary Manager
    1. Writing the Feature
    2. Domain Objects
      1. Employee
      2. SalaryManager
    3. Adding a Test Runner for jUnit
    4. Adding Steps
  5. Directory Structure
  6. Running the Tests
  7. Alternative: jBehave
  8. Tutorial Sources
  9. Resources

Dependencies

To run the following examples, we need the following three dependencies: junit , cucumber-java and cucumber-junit (using cucumber 1.2.0). Using Maven, adding the following snippet to our pom.xml should add everything we need here.

<dependencies>	<dependency>		<groupId>junit</groupId>		<artifactId>junit</artifactId>		<version>4.12</version>		<scope>test</scope>	</dependency>	<dependency>		<groupId>info.cukes</groupId>		<artifactId>cucumber-java</artifactId>		<version>${cucumber.version}</version>		<scope>test</scope>	</dependency>	<dependency>		<groupId>info.cukes</groupId>		<artifactId>cucumber-junit</artifactId>		<version>${cucumber.version}</version>		<scope>test</scope>	</dependency></dependencies>

A simplified Workflow (Suggestion)

  • Get users, developers, testers, product-owners etc.. together
  • They describe the behaviour of a new feature in plain text and using the Gherkin syntax
  • Developers add the derived feature-files to the project and integrate the cucumber-junit-testrunner
  • Run the tests and watch them fail – cucumber prints snippets for the glue code that can be used for writing the step/glue-classes.
  • Write the code to make the first test (step) pass
  • Repeat until everything is green

Example 1: Book Search

Writing the Feature

This is our first feature: A customer shall be able to search books – stored in a file named search_book.feature . Note: Writing possible input parameters in single quotes make it easy for cucumber to generate the glue code for the step files with the correct parameters.

Feature: Book searchTo allow a customer to find his favourite books quickly, the library must offer multiple ways to search for a book. Scenario: Search books by publication yearGiven a book with the title 'One good book', written by 'Anonymous', published in 14 March 2013And another book with the title 'Some other book', written by 'Tim Tomson', published in 23 August 2014And another book with the title 'How to cook a dino', written by 'Fred Flintstone', published in 01 January 2012When the customer searches for books published between 2013 and 2014Then 2 books should have been foundAnd Book 1 should have the title 'Some other book'And Book 2 should have the title 'One good book'

Domain Objects

We have to simple classes here in our domain model: library and book.

Book

This is our simple book class: A book has a title, an author an a publication date.

package com.hascode.tutorial.cucumber.book; import java.util.Date; public class Book {	private final String title;	private final String author;	private final Date published; 	// constructors, getter, setter ommitted}

Library

This is our library class: Our library allows us to add books and find books.

package com.hascode.tutorial.cucumber.book; import java.util.ArrayList;import java.util.Calendar;import java.util.Comparator;import java.util.Date;import java.util.List;import java.util.stream.Collectors; public class Library {	private final List<Book> store = new ArrayList<>(); 	public void addBook(final Book book) {		store.add(book);	} 	public List<Book> findBooks(final Date from, final Date to) {		Calendar end = Calendar.getInstance();		end.setTime(to);		end.roll(Calendar.YEAR, 1); 		return store.stream().filter(book -> {			return from.before(book.getPublished()) && end.getTime().after(book.getPublished());		}).sorted(Comparator.comparing(Book::getPublished).reversed()).collect(Collectors.toList());	}}

Adding a Test Runner for jUnit

We need only one annotation to make the stuff work with jUnit: Cucumber as jUnit runner. I have put the steps, the test-class and the feature-file in similar packages so that the cucumbers automatic scan is working but this behaviour can be overwritten with @CucumberOptions e.g. @CucumberOptions(features={“path-to-features”}..) .

package feature.book; import org.junit.runner.RunWith; import cucumber.api.junit.Cucumber; @RunWith(Cucumber.class)public class BookSearchTest {}

Adding Steps

This is our glue code containing the step definitions. It’s a stateful class that matches steps from the feature description with their parameters to states in our application. The @Format annotation allows us to convert dates in a specific date format into a date parameter.

package feature.book; import static org.hamcrest.MatcherAssert.assertThat;import static org.hamcrest.core.IsEqual.equalTo; import java.util.ArrayList;import java.util.Date;import java.util.List; import com.hascode.tutorial.cucumber.book.Book;import com.hascode.tutorial.cucumber.book.Library; import cucumber.api.Format;import cucumber.api.java.en.Given;import cucumber.api.java.en.Then;import cucumber.api.java.en.When; public class BookSearchSteps {	Library library = new Library();	List<Book> result = new ArrayList<>(); 	@Given(".+book with the title '(.+)', written by '(.+)', published in (.+)")	public void addNewBook(final String title, final String author, @Format("dd MMMMM yyyy") final Date published) {		Book book = new Book(title, author, published);		library.addBook(book);	} 	@When("^the customer searches for books published between (//d+) and (//d+)$")	public void setSearchParameters(@Format("yyyy") final Date from, @Format("yyyy") final Date to) {		result = library.findBooks(from, to);	} 	@Then("(//d+) books should have been found$")	public void verifyAmountOfBooksFound(final int booksFound) {		assertThat(result.size(), equalTo(booksFound));	} 	@Then("Book (//d+) should have the title '(.+)'$")	public void verifyBookAtPosition(final int position, final String title) {		assertThat(result.get(position - 1).getTitle(), equalTo(title));	}}

Example 2: Salary Manager

In the following example, we’ll be using cucumbers data table import to map data from an ASCII table into our domain model.

Writing the Feature

Again we’re starting with a description of the desired behaviour. To enter multiple employees, we’re using a data table as shown here:

Feature: Salary Management Scenario: Modify an employee's salaryGiven the salary management system is initialized with the following data| id| user| salary || 1 | donald| 60000.0|| 2 | dewie | 62000.0|| 3 | goofy | 55000.0|| 4 | scrooge | 70000.0|| 5 | daisy | 56000.0|| 6 | minnie| 62000.0|| 7 | mickey| 51000.0|| 8 | fethry| 66500.0|When the boss increases the salary for the employee with id '3' by 5%Then the payroll for the employee with id '3' should display a salary of 57750

Domain Objects

Again we have two classes: employees and the salary manager.

Employee

An employee has an id, a user-name and a salary, encapsulated in this simple POJO:

package com.hascode.tutorial.cucumber.salary; public class Employee {	private int id;	private String user;	private float salary; 	// constructor, getter, setter ommitted}

SalaryManager

The salary manager allows to increase an employee’s salary and to find an employee by his id. The list of know employees is passed as constructor parameters so that the simple for-tutorials-only implementation looks like this:

package com.hascode.tutorial.cucumber.salary; import java.util.HashMap;import java.util.List;import java.util.Map;import java.util.function.Function;import java.util.stream.Collectors; public class SalaryManager {	private Map<Integer, Employee> employees = new HashMap<>(); 	public SalaryManager(final List<Employee> employees) {		this.employees = employees.stream().collect(Collectors.toMap(Employee::getId, Function.<Employee> identity()));	} 	public void increaseSalary(final Integer id, final int increaseInPercent) {		Employee nominee = employees.get(id);		float oldSalary = nominee.getSalary();		nominee.setSalary(oldSalary + oldSalary * increaseInPercent / 100);	} 	public Employee getPayroll(final int id) {		return employees.get(id);	}}

Adding a Test Runner for jUnit

Again we’re adding this starting point for jUnit:

package feature.salary; import org.junit.runner.RunWith; import cucumber.api.junit.Cucumber; @RunWith(Cucumber.class)public class SalaryTest { }

Adding Steps

We’re running the tests, see them fail, have a look in the console and copy and paste the generated cucumber code. Afterwards we’re implementing the rest until we seen the green light

An interesting feature is the ability to map the data table into a collection of a mappable element. The first method annotated with @Given receives a generic list of employees as a parameter  so that the field of the data table are mapped to their equivalent fields in the employee class.

package feature.salary; import static org.hamcrest.MatcherAssert.assertThat;import static org.hamcrest.core.IsEqual.equalTo; import java.util.List; import com.hascode.tutorial.cucumber.salary.Employee;import com.hascode.tutorial.cucumber.salary.SalaryManager; import cucumber.api.java.en.Given;import cucumber.api.java.en.Then;import cucumber.api.java.en.When; public class SalarySteps {	SalaryManager manager; 	@Given("^the salary management system is initialized with the following data$")	public void the_salary_management_system_is_initialized_with_the_following_data(final List<Employee> employees) throws Throwable {		manager = new SalaryManager(employees);	} 	@When("^the boss increases the salary for the employee with id '(//d+)' by (//d+)%$")	public void the_boss_increases_the_salary_for_the_employee_with_id_by(final int id, final int increaseInPercent) throws Throwable {		manager.increaseSalary(id, increaseInPercent);	} 	@Then("^the payroll for the employee with id '(//d+)' should display a salary of (//d+)$")	public void the_payroll_for_the_employee_with_id_should_display_a_salary_of(final int id, final float salary) throws Throwable {		Employee nominee = manager.getPayroll(id);		assertThat(nominee.getSalary(), equalTo(salary));	}}

Directory Structure

The directory structure with the examples described above looks  like this one:

.├── pom.xml└── src	├── main	│ ├── java	│ │ └── com	│ │	 └── hascode	│ │		 └── tutorial	│ │			 └── cucumber	│ │				 ├── book	│ │				 │ ├── Book.java	│ │				 │ └── Library.java	│ │				 └── salary	│ │					 ├── Employee.java	│ │					 └── SalaryManager.java	│ └── resources	└── test		├── java		│ └── feature		│	 ├── book		│	 │ ├── BookSearchSteps.java		│	 │ └── BookSearchTest.java		│	 └── salary		│		 ├── SalarySteps.java		│		 └── SalaryTest.java		└── resources			└── feature				├── book				│ └── search_book.feature				└── salary					└── salary_management.feature

Running the Tests

Now we’re ready to run the tests – using Maven it’s done like this:

$ mvn test------------------------------------------------------- T E S T S-------------------------------------------------------Running feature.book.BookSearchTest 1 Scenarios (1 passed)7 Steps (7 passed)0m0.122s Tests run: 8, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.375 secRunning feature.salary.SalaryTest 1 Scenarios (1 passed)3 Steps (3 passed)0m0.018s Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.033 sec Results : Tests run: 12, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------

Otherwise we can use our favourite IDE’s jUnit Runner – this is what running the tests looks like in Eclipse IDE:

Running a Cucumber Test with jUnit in Eclipse IDE

Alternative: jBehave

JBehave is an alternative framework for writing BDD tests using the Gherkin syntax an with annotation driven mapping between the text format of a story and the glue code to make the tests work. I’ve written an article a while ago about this framework so please feel free to have a look if interested: “ Oh JBehave, Baby! Behaviour Driven Development using JBehave “.

Tutorial Sources

Please feel free to download the tutorial sources from my Bitbucket repository , fork it there or clone it using Git:

git clone https://bitbucket.org/hascode/cucumber-java-tutorial.git

Resources

  • Cucumber Project Website
  • Cucumber on GitHub
  • Gherkin Syntax Documentation
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
How to Install Eclipse for Java Programming on Windows, Mac and Linux
maven
Perl IDE and Programming Environment
怎样让Selenium IDE录制的脚本转为java语言在eclipse中执行
Cucumber使用进阶
Eclipse 安装插件 | 菜鸟教程
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服