Pengantar DBUnit

1. Perkenalan

Dalam tutorial ini, kita akan melihat DBUnit, alat pengujian unit yang digunakan untuk menguji interaksi database relasional di Java.

Kita akan melihat bagaimana hal itu membantu kita mendapatkan database kita ke keadaan yang diketahui dan menegaskan terhadap keadaan yang diharapkan.

2. Dependensi

Pertama, kita dapat menambahkan DBUnit ke proyek kita dari Maven Central dengan menambahkan ketergantungan dbunit ke pom.xml kita :

 org.dbunit dbunit 2.7.0 test 

Kami dapat mencari versi terbaru di Maven Central.

3. Contoh Hello World

Selanjutnya, mari kita tentukan skema database:

schema.sql :

CREATE TABLE IF NOT EXISTS CLIENTS ( `id` int AUTO_INCREMENT NOT NULL, `first_name` varchar(100) NOT NULL, `last_name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS ITEMS ( `id` int AUTO_INCREMENT NOT NULL, `title` varchar(100) NOT NULL, `produced` date, `price` float, PRIMARY KEY (`id`) ); 

3.1. Mendefinisikan Isi Awal Database

DBUnit memungkinkan kita mendefinisikan dan memuat set data uji kita dengan cara deklaratif sederhana .

Kami mendefinisikan setiap baris tabel dengan satu elemen XML, di mana nama tag adalah nama tabel, dan nama atribut dan nilai masing-masing dipetakan ke nama kolom dan nilai. Data baris dapat dibuat untuk beberapa tabel. Kita harus mengimplementasikan metode getDataSet () dari DataSourceBasedDBTestCase untuk menentukan kumpulan data awal, di mana kita dapat menggunakan FlatXmlDataSetBuilder untuk merujuk ke file XML kita:

data.xml :

3.2. Menginisialisasi Koneksi dan Skema Database

Sekarang setelah kita mendapatkan skema kita, kita harus menginisialisasi database kita.

Kita harus memperluas kelas DataSourceBasedDBTestCase dan menginisialisasi skema database dalam metode getDataSource () :

DataSourceDBUnitTest.java :

public class DataSourceDBUnitTest extends DataSourceBasedDBTestCase { @Override protected DataSource getDataSource() { JdbcDataSource dataSource = new JdbcDataSource(); dataSource.setURL( "jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;init=runscript from 'classpath:schema.sql'"); dataSource.setUser("sa"); dataSource.setPassword("sa"); return dataSource; } @Override protected IDataSet getDataSet() throws Exception { return new FlatXmlDataSetBuilder().build(getClass().getClassLoader() .getResourceAsStream("data.xml")); } }

Di sini, kami meneruskan file SQL ke database dalam memori H2 dalam string koneksinya. Jika kami ingin menguji pada database lain, kami perlu menyediakan implementasi kustom kami untuk itu.

Perlu diingat bahwa , dalam contoh kami, DBUnit akan menginisialisasi ulang database dengan data pengujian yang diberikan sebelum setiap eksekusi metode pengujian .

Ada beberapa cara untuk mengonfigurasinya melalui get SetUpOperation dan dapatkan TearDownOperation :

@Override protected DatabaseOperation getSetUpOperation() { return DatabaseOperation.REFRESH; } @Override protected DatabaseOperation getTearDownOperation() { return DatabaseOperation.DELETE_ALL; }

The REFRESH operasi, mengatakan DBUnit untuk me-refresh semua datanya. Ini akan memastikan bahwa semua cache dibersihkan dan pengujian unit kami tidak mendapat pengaruh dari pengujian unit lain. The DELETE_ALL menjamin operasi bahwa semua data akan dihapus pada akhir setiap unit test. Dalam kasus kami, kami memberi tahu DBUnit bahwa selama penyiapan, menggunakan implementasi metode getSetUpOperation kami akan menyegarkan semua cache. Terakhir, kami memberi tahu DBUnit untuk menghapus semua data selama operasi pembongkaran menggunakan implementasi metode getTearDownOperation .

3.3. Membandingkan Negara yang Diharapkan dan Negara Sebenarnya

Sekarang, mari kita periksa kasus uji kita yang sebenarnya. Untuk pengujian pertama ini, kami akan membuatnya sederhana - kami akan memuat kumpulan data yang kami harapkan dan membandingkannya dengan kumpulan data yang diambil dari koneksi DB kami:

@Test public void givenDataSetEmptySchema_whenDataSetCreated_thenTablesAreEqual() throws Exception { IDataSet expectedDataSet = getDataSet(); ITable expectedTable = expectedDataSet.getTable("CLIENTS"); IDataSet databaseDataSet = getConnection().createDataSet(); ITable actualTable = databaseDataSet.getTable("CLIENTS"); assertEquals(expectedTable, actualTable); }

4. Mendalami Pernyataan

Di bagian sebelumnya, kami melihat contoh dasar untuk membandingkan konten tabel yang sebenarnya dengan kumpulan data yang diharapkan. Sekarang kita akan menemukan dukungan DBUnit untuk menyesuaikan pernyataan data.

4.1. Menegaskan dengan SQL Query

Cara langsung untuk memeriksa keadaan sebenarnya adalah dengan kueri SQL .

Dalam contoh ini, kita akan memasukkan record baru ke dalam tabel CLIENTS, lalu memverifikasi konten baris yang baru dibuat. Kami mendefinisikan output yang diharapkan dalam file XML terpisah , dan mengekstrak nilai baris sebenarnya dengan kueri SQL:

@Test public void givenDataSet_whenInsert_thenTableHasNewClient() throws Exception { try (InputStream is = getClass().getClassLoader().getResourceAsStream("dbunit/expected-user.xml")) { IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(is); ITable expectedTable = expectedDataSet.getTable("CLIENTS"); Connection conn = getDataSource().getConnection(); conn.createStatement() .executeUpdate( "INSERT INTO CLIENTS (first_name, last_name) VALUES ('John', 'Jansen')"); ITable actualData = getConnection() .createQueryTable( "result_name", "SELECT * FROM CLIENTS WHERE last_name="Jansen""); assertEqualsIgnoreCols(expectedTable, actualData, new String[] { "id" }); } }

Metode getConnection () dari kelas leluhur DBTestCase mengembalikan representasi khusus DBUnit dari koneksi sumber data ( instance IDatabaseConnection ). Metode createQueryTable () dari IDatabaseConnection bisa digunakan untuk mengambil data aktual dari database , untuk perbandingan dengan status database yang diharapkan, menggunakan metode Assertion.assertEquals () . Kueri SQL yang diteruskan ke createQueryTable () adalah kueri yang ingin kami uji. Ini mengembalikan contoh Tabel yang kita gunakan untuk membuat pernyataan kita.

4.2. Mengabaikan Kolom

Terkadang dalam pengujian database, kami ingin mengabaikan beberapa kolom dari tabel sebenarnya . Ini biasanya adalah nilai yang dibuat secara otomatis yang tidak dapat kami kontrol dengan ketat, seperti kunci utama yang dibuat atau stempel waktu saat ini .

Kita bisa melakukan ini dengan menghilangkan kolom dari klausa SELECT di kueri SQL, tetapi DBUnit menyediakan utilitas yang lebih nyaman untuk mencapai ini. Dengan metode statis kelas DefaultColumnFilter kita dapat membuat instance ITable baru dari yang sudah ada dengan mengecualikan beberapa kolom , seperti yang ditunjukkan di sini:

@Test public void givenDataSet_whenInsert_thenGetResultsAreStillEqualIfIgnoringColumnsWithDifferentProduced() throws Exception { Connection connection = tester.getConnection().getConnection(); String[] excludedColumns = { "id", "produced" }; try (InputStream is = getClass().getClassLoader() .getResourceAsStream("dbunit/expected-ignoring-registered_at.xml")) { IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(is); ITable expectedTable = excludedColumnsTable(expectedDataSet.getTable("ITEMS"), excludedColumns); connection.createStatement() .executeUpdate("INSERT INTO ITEMS (title, price, produced) VALUES('Necklace', 199.99, now())"); IDataSet databaseDataSet = tester.getConnection().createDataSet(); ITable actualTable = excludedColumnsTable(databaseDataSet.getTable("ITEMS"), excludedColumns); assertEquals(expectedTable, actualTable); } }

4.3. Menyelidiki Banyak Kegagalan

Jika DBUnit menemukan nilai yang salah, maka DBUnit akan segera melempar AssertionError .

In specific cases, we can use the DiffCollectingFailureHandler class, which we can pass to the Assertion.assertEquals() method as a third argument.

This failure handler will collect all failures instead of stopping on the first one, meaning that the Assertion.assertEquals() method will always succeed if we use the DiffCollectingFailureHandler. Therefore, we'll have to programmatically check if the handler found any errors:

@Test public void givenDataSet_whenInsertUnexpectedData_thenFailOnAllUnexpectedValues() throws Exception { try (InputStream is = getClass().getClassLoader() .getResourceAsStream("dbunit/expected-multiple-failures.xml")) { IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(is); ITable expectedTable = expectedDataSet.getTable("ITEMS"); Connection conn = getDataSource().getConnection(); DiffCollectingFailureHandler collectingHandler = new DiffCollectingFailureHandler(); conn.createStatement() .executeUpdate("INSERT INTO ITEMS (title, price) VALUES ('Battery', '1000000')"); ITable actualData = getConnection().createDataSet().getTable("ITEMS"); assertEquals(expectedTable, actualData, collectingHandler); if (!collectingHandler.getDiffList().isEmpty()) { String message = (String) collectingHandler.getDiffList() .stream() .map(d -> formatDifference((Difference) d)) .collect(joining("\n")); logger.error(() -> message); } } } private static String formatDifference(Difference diff) { return "expected value in " + diff.getExpectedTable() .getTableMetaData() .getTableName() + "." + diff.getColumnName() + " row " + diff.getRowIndex() + ":" + diff.getExpectedValue() + ", but was: " + diff.getActualValue(); }

Furthermore, the handler provides the failures in the form of Difference instances, which lets us format the errors.

After running the test we get a formatted report:

java.lang.AssertionError: expected value in ITEMS.price row 5:199.99, but was: 1000000.0 expected value in ITEMS.produced row 5:2019-03-23, but was: null expected value in ITEMS.title row 5:Necklace, but was: Battery at com.baeldung.dbunit.DataSourceDBUnitTest.givenDataSet_whenInsertUnexpectedData_thenFailOnAllUnexpectedValues(DataSourceDBUnitTest.java:91)

Penting untuk diperhatikan bahwa pada tahap ini kami mengharapkan item baru memiliki harga 199,99 tetapi ternyata 1000000.0. Kemudian kami melihat bahwa tanggal produksi menjadi 2019-03-23, tetapi pada akhirnya, itu nol. Akhirnya, item yang diharapkan adalah Kalung dan sebagai gantinya kami mendapat Baterai.

5. Kesimpulan

Dalam artikel ini, kami melihat bagaimana DBUnit menyediakan cara deklaratif untuk menentukan data pengujian untuk menguji lapisan akses data aplikasi Java.

Seperti biasa, kode sumber lengkap untuk contoh tersedia di GitHub.