Tabel Data Mentimun

1. Perkenalan

Mentimun adalah kerangka kerja Behavioral Driven Development (BDD) yang memungkinkan pengembang membuat skenario pengujian berbasis teks menggunakan bahasa Gherkin. Dalam banyak kasus, skenario ini memerlukan data tiruan untuk menjalankan fitur, yang dapat merepotkan untuk dimasukkan - terutama dengan entri yang kompleks atau banyak.

Dalam tutorial ini, kita akan melihat cara menggunakan tabel data Ketimun untuk menyertakan data tiruan dengan cara yang dapat dibaca.

2. Sintaks Skenario

Saat menentukan skenario Mentimun, kami sering memasukkan data pengujian yang digunakan oleh skenario lainnya:

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. Tabel Data

Meskipun data sebaris cukup untuk satu buku, skenario kami dapat menjadi berantakan saat menambahkan beberapa buku. Untuk menangani ini, kami membuat tabel data dalam skenario 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 mendefinisikan tabel data kami sebagai bagian dari klausa Diberikan kami dengan membuat indentasi tabel di bawah teks klausa Diberikan . Dengan menggunakan tabel data ini, kita dapat menambahkan sejumlah buku secara acak - termasuk hanya satu buku - ke toko kita dengan menambahkan atau menghapus baris.

Selain itu, tabel data dapat digunakan dengan klausa apa pun - tidak hanya klausa Diberikan .

2.2. Termasuk Judul

Jelaslah bahwa kolom pertama mewakili judul buku, dan kolom kedua mewakili penulis buku tersebut. Arti setiap kolom tidak selalu begitu jelas.

Jika klarifikasi diperlukan, kami dapat menyertakan header dengan menambahkan baris pertama 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

Meskipun tajuk tampaknya hanya berupa baris lain dalam tabel, baris pertama ini memiliki arti khusus saat kita mengurai tabel menjadi daftar peta di bagian berikutnya.

3. Definisi Langkah

Setelah membuat skenario kami, kami menerapkan definisi langkah yang Diberikan . Dalam kasus langkah yang berisi tabel data, kami mengimplementasikan metode kami dengan argumen DataTable :

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

The DataTable objek berisi data tabular dari tabel data yang kita didefinisikan dalam skenario kami, serta metode untuk mengubah data ini menjadi informasi yang dapat digunakan . Secara umum, ada tiga cara untuk mengubah tabel data di Ketimun: (1) daftar daftar, (2) daftar peta, dan (3) transformator tabel.

Untuk mendemonstrasikan setiap teknik, kita akan menggunakan kelas domain Book sederhana :

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

Selain itu, kami akan membuat kelas BookStore yang mengelola objek Book :

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 skenario berikut, kita akan mulai dengan definisi langkah dasar:

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

3.1. Daftar Daftar

Metode paling dasar untuk menangani data tabular adalah mengubah argumen DataTable menjadi daftar daftar. Kita dapat membuat tabel tanpa header 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

Mentimun mengonversi tabel di atas menjadi daftar dengan memperlakukan setiap baris sebagai daftar nilai kolom . Dengan demikian, Mentimun mengurai setiap baris menjadi daftar yang berisi judul buku sebagai elemen pertama dan penulis sebagai elemen 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 metode asLists - menyediakan argumen String.class - untuk mengubah argumen DataTable menjadi List . Argumen Class ini memberi tahu metode asLists tipe data apa yang kita harapkan dari setiap elemen . Dalam kasus kami, kami ingin judul dan penulis menjadi nilai String . Jadi, 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 dari sub-daftar dan membuat objek Book yang sesuai . Terakhir, kami menambahkan setiap objek Book yang dibuat ke objek BookStore kami .

Jika kita mengurai data yang berisi sebuah heading, kita akan melewati baris pertama karena Mentimun tidak membedakan antara data heading dan baris untuk daftar list.

3.2. Daftar Peta

Meskipun daftar daftar menyediakan mekanisme dasar untuk mengekstraksi elemen dari tabel data, langkah implementasi bisa jadi samar. Mentimun memberikan daftar mekanisme peta sebagai alternatif yang lebih mudah dibaca.

Dalam hal ini, kita harus memberikan judul untuk tabel 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

Mirip dengan mekanisme daftar, Mentimun membuat daftar yang berisi setiap baris, tetapi memetakan judul kolom ke setiap nilai kolom . Mentimun 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.

Meskipun daftar daftar dan daftar peta cukup untuk tabel dasar, transformator tabel menyediakan mekanisme yang lebih kaya yang mampu menangani data yang lebih kompleks.

Kode sumber lengkap artikel ini dapat ditemukan di GitHub.