Jadual Data Timun

1. Pengenalan

Cucumber adalah kerangka Behavioral Driven Development (BDD) yang membolehkan pembangun membuat senario ujian berasaskan teks menggunakan bahasa Gherkin. Dalam banyak kes, senario ini memerlukan data palsu untuk menggunakan satu ciri, yang boleh menyuntikkan untuk disuntikkan - terutamanya dengan entri yang kompleks atau berganda.

Dalam tutorial ini, kita akan melihat bagaimana menggunakan jadual data Timun untuk memasukkan data tiruan dengan cara yang mudah dibaca.

2. Sintaksis Senario

Semasa menentukan senario timun, kami sering memasukkan data ujian yang digunakan oleh senario lain:

Scenario: Correct non-zero number of books found by author Given I have the a book in the store called The Devil in the White City by Erik Larson When I search for books by author Erik Larson Then I find 1 book

2.1. Jadual Data

Walaupun data sebaris mencukupi untuk satu buku, senario kita dapat menjadi berantakan ketika menambahkan banyak buku. Untuk mengatasinya, kami membuat jadual data dalam senario kami:

Scenario: Correct non-zero number of books found by author Given I have the following books in the store | The Devil in the White City | Erik Larson | | The Lion, the Witch and the Wardrobe | C.S. Lewis | | In the Garden of Beasts | Erik Larson | When I search for books by author Erik Larson Then I find 2 books

Kami menentukan jadual data kami sebagai bahagian dari klausa Diberi kami dengan menandakan jadual di bawah teks klausa Diberi . Dengan menggunakan jadual data ini, kami dapat menambahkan sebilangan buku sewenang-wenangnya - termasuk hanya satu buku - ke kedai kami dengan menambahkan atau membuang baris.

Selain itu, jadual data boleh digunakan dengan klausa apa pun - bukan hanya Klausa yang diberikan .

2.2. Termasuk Tajuk

Jelas bahawa lajur pertama mewakili tajuk buku, dan lajur kedua mewakili pengarang buku. Makna setiap lajur tidak selalu begitu jelas.

Apabila penjelasan diperlukan, kami dapat memasukkan tajuk dengan menambahkan baris pertama yang baru :

Scenario: Correct non-zero number of books found by author Given I have the following books in the store | title | author | | The Devil in the White City | Erik Larson | | The Lion, the Witch and the Wardrobe | C.S. Lewis | | In the Garden of Beasts | Erik Larson | When I search for books by author Erik Larson Then I find 2 books

Walaupun header nampaknya hanya baris lain dalam jadual, baris pertama ini mempunyai arti khusus ketika kita menguraikan jadual kita ke dalam senarai peta di bahagian seterusnya.

3. Definisi Langkah

Setelah membuat senario kami, kami melaksanakan definisi langkah Diberi . Sekiranya terdapat langkah yang berisi tabel data, kami menerapkan metode kami dengan argumen DataTable :

@Given("some phrase") public void somePhrase(DataTable table) { // ... }

The DataTable objek mengandungi data tabular dari jadual data yang kami ditakrifkan dalam senario kita, dan juga kaedah untuk mengubah data ini ke dalam maklumat yang boleh digunakan . Secara umum, ada tiga cara untuk mengubah tabel data dalam Timun: (1) daftar daftar, (2) daftar peta, dan (3) pengubah meja.

Untuk menunjukkan setiap teknik, kami akan menggunakan kelas domain Buku ringkas :

public class Book { private String title; private String author; // standard constructors, getters & setters ... }

Selain itu, kami akan membuat kelas BookStore yang menguruskan objek Buku :

public class BookStore { private List books = new ArrayList(); public void addBook(Book book) { books.add(book); } public void addAllBooks(Collection books) { this.books.addAll(books); } public List booksByAuthor(String author) { return books.stream() .filter(book -> Objects.equals(author, book.getAuthor())) .collect(Collectors.toList()); } }

Untuk setiap senario berikut, kami akan memulakan dengan definisi langkah asas:

public class BookStoreRunSteps { private BookStore store; private List foundBooks; @Before public void setUp() { store = new BookStore(); foundBooks = new ArrayList(); } // When & Then definitions ... }

3.1. Senarai Senarai

Kaedah yang paling asas untuk mengendalikan data tabular adalah menukar argumen DataTable menjadi senarai senarai. Kita boleh membuat jadual tanpa tajuk untuk menunjukkan:

Scenario: Correct non-zero number of books found by author by list Given I have the following books in the store by list | The Devil in the White City | Erik Larson | | The Lion, the Witch and the Wardrobe | C.S. Lewis | | In the Garden of Beasts | Erik Larson | When I search for books by author Erik Larson Then I find 2 books

Timun menukar jadual di atas menjadi senarai senarai dengan memperlakukan setiap baris sebagai senarai nilai lajur . Oleh itu, Timun menguraikan setiap baris ke dalam senarai yang mengandungi tajuk buku sebagai elemen pertama dan pengarang sebagai yang kedua:

[ ["The Devil in the White City", "Erik Larson"], ["The Lion, the Witch and the Wardrobe", "C.S. Lewis"], ["In the Garden of Beasts", "Erik Larson"] ]

Kami menggunakan kaedah asLists - menyediakan argumen String.class - untuk menukar argumen DataTable ke Daftar . Ini Kelas memaklumi hujah asLists kaedah apa menaip data kami menjangkakan setiap elemen untuk menjadi . Dalam kes kami, kami mahu tajuk dan pengarang menjadi nilai String . Oleh itu, kami menyediakan String.class :

@Given("^I have the following books in the store by list$") public void haveBooksInTheStoreByList(DataTable table) { List
    
      rows = table.asLists(String.class); for (List columns : rows) { store.addBook(new Book(columns.get(0), columns.get(1))); } }
    

Kami kemudian mengulangi setiap elemen sub-senarai dan membuat objek Buku yang sesuai . Terakhir, kami menambahkan setiap objek Buku yang dibuat ke objek BookStore kami .

Sekiranya kita menguraikan data yang berisi tajuk, kita akan melewati baris pertama kerana Timun tidak membezakan antara tajuk dan data baris untuk senarai senarai.

3.2. Senarai Peta

Walaupun senarai senarai menyediakan mekanisme asas untuk mengekstrak elemen dari jadual data, pelaksanaan langkahnya mungkin samar. Timun menyediakan senarai mekanisme peta sebagai alternatif yang lebih mudah dibaca.

Dalam kes ini, kita mesti memberikan tajuk jadual kita :

Scenario: Correct non-zero number of books found by author by map Given I have the following books in the store by map | title | author | | The Devil in the White City | Erik Larson | | The Lion, the Witch and the Wardrobe | C.S. Lewis | | In the Garden of Beasts | Erik Larson | When I search for books by author Erik Larson Then I find 2 books

Sama seperti mekanisme senarai senarai, Timun membuat senarai yang mengandungi setiap baris tetapi memetakan tajuk lajur ke setiap nilai lajur . Timun mengulangi proses ini untuk setiap baris berikutnya:

[ {"title": "The Devil in the White City", "author": "Erik Larson"}, {"title": "The Lion, the Witch and the Wardrobe", "author": "C.S. Lewis"}, {"title": "In the Garden of Beasts", "author": "Erik Larson"} ]

We use the asMaps method — supplying two String.class arguments — to convert the DataTable argument to a List. The first argument denotes the data type of the key (header) and second indicates the data type of each column value. Thus, we supply two String.class arguments because our headers (key) and title and author (values) are all Strings.

Then we iterate over each Map object and extract each column value using the column header as the key:

@Given("^I have the following books in the store by map$") public void haveBooksInTheStoreByMap(DataTable table) { List rows = table.asMaps(String.class, String.class); for (Map columns : rows) { store.addBook(new Book(columns.get("title"), columns.get("author"))); } }

3.3. Table Transformer

The final (and most rich) mechanism for converting data tables to usable objects is to create a TableTransformer. A TableTransformer is an object that instructs Cucumber how to convert a DataTable object to the desired domain object:

Let's see an example scenario:

Scenario: Correct non-zero number of books found by author with transformer Given I have the following books in the store with transformer | title | author | | The Devil in the White City | Erik Larson | | The Lion, the Witch and the Wardrobe | C.S. Lewis | | In the Garden of Beasts | Erik Larson | When I search for books by author Erik Larson Then I find 2 books

While a list of maps, with its keyed column data, is more precise than a list of lists, we still clutter our step definition with conversion logic. Instead, we should define our step with the desired domain object (in this case, a BookCatalog) as an argument:

@Given("^I have the following books in the store with transformer$") public void haveBooksInTheStoreByTransformer(BookCatalog catalog) { store.addAllBooks(catalog.getBooks()); }

To do this, we must create a custom implementation of the TypeRegistryConfigurer interface.

This implementation must perform two things:

  1. Create a new TableTransformer implementation.
  2. Register this new implementation using the configureTypeRegistry method.

To capture the DataTable into a useable domain object, we'll create a BookCatalog class:

public class BookCatalog { private List books = new ArrayList(); public void addBook(Book book) { books.add(book); } // standard getter ... }

To perform the transformation, let's implement the TypeRegistryConfigurer interface:

public class BookStoreRegistryConfigurer implements TypeRegistryConfigurer { @Override public Locale locale() { return Locale.ENGLISH; } @Override public void configureTypeRegistry(TypeRegistry typeRegistry) { typeRegistry.defineDataTableType( new DataTableType(BookCatalog.class, new BookTableTransformer()) ); } //...

and then implement the TableTransformer interface for our BookCatalog class:

 private static class BookTableTransformer implements TableTransformer { @Override public BookCatalog transform(DataTable table) throws Throwable { BookCatalog catalog = new BookCatalog(); table.cells() .stream() .skip(1) // Skip header row .map(fields -> new Book(fields.get(0), fields.get(1))) .forEach(catalog::addBook); return catalog; } } }

Note that we're transforming English data from the table, and therefore, we return the English locale from our locale() method. When parsing data in a different locale, we must change the return type of the locale() method to the appropriate locale.

Since we included a data table header in our scenario, we must skip the first row when iterating over the table cells (hence the skip(1) call). We would remove the skip(1) call if our table did not include a header.

By default, the glue code associated with a test is assumed to be in the same package as the runner class. Therefore, no additional configuration is needed if we include our BookStoreRegistryConfigurer in the same package as our runner class. If we add the configurer in a different package, we must explicitly include the package in the @CucumberOptionsglue field for the runner class.

4. Conclusion

In this article, we looked at how to define a Gherkin scenario with tabular data using a data table. Additionally, we explored three ways of implementing a step definition that consumes a Cucumber data table.

Walaupun senarai senarai dan senarai peta mencukupi untuk jadual asas, pengubah jadual menyediakan mekanisme yang lebih kaya yang mampu menangani data yang lebih kompleks.

Kod sumber lengkap artikel ini boleh didapati di GitHub.