Pengenalan DBUnit

1. Pengenalan

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

Kami akan melihat bagaimana ia membantu kami membuat pangkalan data kami ke keadaan yang diketahui dan menegaskan terhadap keadaan yang diharapkan.

2. Kebergantungan

Pertama, kita dapat menambahkan DBUnit ke projek kita dari Maven Central dengan menambahkan kebergantungan dbunit ke pom.xml kami :

 org.dbunit dbunit 2.7.0 test 

Kita boleh mencari versi terbaru di Maven Central.

3. Hello World Contoh

Seterusnya, mari kita tentukan skema pangkalan data:

skema.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. Menentukan Kandungan Pangkalan Data Permulaan

DBUnit membolehkan kita menentukan dan memuat set data ujian kami dengan cara deklaratif yang mudah .

Kami menentukan setiap baris jadual dengan satu elemen XML, di mana nama tag adalah nama jadual, dan nama atribut dan nilai peta masing-masing ke nama dan nilai lajur. Data baris boleh dibuat untuk beberapa jadual. Kita perlu melaksanakan getDataSet () kaedah DataSourceBasedDBTestCase untuk menentukan set data awal, di mana kita boleh menggunakan FlatXmlDataSetBuilder untuk merujuk kepada fail XML kami:

data.xml :

3.2. Memulakan Sambungan dan Skema Pangkalan Data

Sekarang kita mempunyai skema, kita harus memulakan pangkalan data kita.

Kita harus memperluas kelas DataSourceBasedDBTestCase dan memulakan skema pangkalan data dalam kaedah 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 fail SQL ke pangkalan data dalam memori H2 dalam rentetan sambungannya. Sekiranya kita ingin menguji pangkalan data lain, kita perlu menyediakan pelaksanaan khusus untuknya.

Perlu diingat bahawa , dalam contoh kami, DBUnit akan memulakan semula pangkalan data dengan data ujian yang diberikan sebelum setiap pelaksanaan kaedah ujian .

Terdapat pelbagai cara untuk mengkonfigurasinya melalui get SetUpOperation dan dapatkan TearDownOperation :

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

The REFRESH operasi, memberitahu DBUnit untuk memuat semula semua data. Ini akan memastikan bahawa semua cache dibersihkan dan ujian unit kami tidak mendapat pengaruh daripada ujian unit lain. The DELETE_ALL memastikan operasi bahawa semua data yang akan dikeluarkan pada akhir setiap ujian unit. Dalam kes kami, kami memberitahu DBUnit bahawa semasa penyiapan, menggunakan pelaksanaan kaedah getSetUpOperation, kami akan menyegarkan semua cache. Akhirnya, kami memberitahu DBUnit untuk menghapus semua data semasa operasi teardown menggunakan pelaksanaan kaedah getTearDownOperation .

3.3. Membandingkan Negeri yang Diharapkan dan Negara Sebenar

Sekarang, mari kita periksa kes ujian sebenar kita. Untuk ujian pertama ini, kami akan mempermudahnya - kami akan memuatkan set data yang diharapkan dan membandingkannya dengan set data yang diambil dari sambungan 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. Menyelam Dalam Teguran

Pada bahagian sebelumnya, kami melihat contoh asas membandingkan kandungan sebenar jadual dengan set data yang diharapkan. Sekarang kita akan menemui sokongan DBUnit untuk menyesuaikan penegasan data.

4.1. Menegaskan dengan Pertanyaan SQL

Cara mudah untuk memeriksa keadaan sebenar adalah dengan pertanyaan SQL .

Dalam contoh ini, kami akan memasukkan rekod baru ke dalam jadual KLIEN, kemudian mengesahkan kandungan baris yang baru dibuat. Kami menentukan output yang diharapkan dalam fail XML yang berasingan , dan mengekstrak nilai baris sebenar dengan pertanyaan 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" }); } }

Kaedah getConnection () dari kelas leluhur DBTestCase mengembalikan perwakilan khusus DBUnit dari sambungan sumber data ( contoh IDatabaseConnection ). Kaedah createQueryTable () dari IDatabaseConnection dapat digunakan untuk mengambil data sebenar dari pangkalan data , untuk perbandingan dengan keadaan pangkalan data yang diharapkan, menggunakan kaedah Assertion.assertEquals () . Pertanyaan SQL yang diteruskan ke createQueryTable () adalah pertanyaan yang ingin kami uji. Ia mengembalikan contoh Jadual yang kami gunakan untuk membuat penegasan kami.

4.2. Mengabaikan Lajur

Kadang-kadang dalam ujian pangkalan data, kami ingin mengabaikan beberapa lajur jadual sebenar . Ini biasanya merupakan nilai yang dihasilkan secara automatik yang tidak dapat kita kendalikan dengan ketat, seperti kunci utama yang dihasilkan atau cap waktu semasa .

Kami dapat melakukan ini dengan menghilangkan lajur dari klausa PILIH dalam pertanyaan SQL, tetapi DBUnit menyediakan utiliti yang lebih mudah untuk mencapainya. Dengan kaedah statik kelas DefaultColumnFilter kita dapat membuat contoh ITable baru dari yang ada dengan mengecualikan beberapa lajur , 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. Menyiasat Kegagalan Berganda

Sekiranya DBUnit menemui nilai yang tidak betul, maka dengan serta-merta melemparkan 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 bahawa pada ketika ini kami menjangkakan item baru akan mempunyai harga 199.99 tetapi harga 1000000.0. Kemudian kita melihat bahawa tarikh pengeluaran akan 2019-03-23, tetapi pada akhirnya, ia adalah sia-sia. Akhirnya, item yang diharapkan adalah Kalung dan sebaliknya kami mendapat Bateri.

5. Kesimpulan

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

Seperti biasa, kod sumber penuh untuk contoh boleh didapati di GitHub.