Bagaimana cara menguji lapisan akses data?

17

Saya memiliki metode DAO yang menggunakan Spring untuk akses JDBC. Ini menghitung tingkat keberhasilan penjual dalam menjual suatu barang.

Ini kodenya:

public BigDecimal getSellingSuccessRate(long seller_id) {
    String sql = "SELECT SUM(IF(sold_price IS NOT NULL, 1, 0))/SUM(1) 
                  FROM transaction WHERE seller_id = ?";
    Object[] args = {seller_id};
    return getJdbcTemplate().queryForObject(sql, args, BigDecimal.class);
}

Bagaimana cara saya menguji metode ini atau metode DAO dengan JUnit? Apa sajakah praktik terbaik untuk menguji logika akses data? Saya berpikir untuk mengujinya terhadap basis data yang dapat di-embed yang memuat beberapa data, tetapi bukankah kita harus melakukan tes integrasi yang mirip dengan lingkungan produksi dalam hal RDBMS dan skema?

Michael
sumber
Lihat DBUnit . Itu dibuat khusus untuk menyelesaikan masalah Anda.
Sergio

Jawaban:

15

Masalah dengan menggunakan database 'nyata' untuk pengujian unit adalah pengaturan, mencatat, dan isolasi tes. Anda tidak ingin harus memutar basis data MySQL yang sama sekali baru dan membuat tabel dan data hanya untuk satu tes unit. Masalah dengan ini berkaitan dengan sifat eksternal dari basis data dan basis data pengujian Anda turun, pengujian unit Anda gagal. Ada juga masalah dengan memastikan Anda memiliki database unik untuk pengujian. Mereka dapat diatasi, tetapi ada jawaban yang lebih sederhana.

Mengejek basis data adalah salah satu opsi namun tidak menguji kueri aktual yang dijalankan. Ini dapat digunakan sebagai solusi yang lebih sederhana ketika Anda ingin memastikan data dari DAO melewati sistem dengan benar. Tetapi untuk menguji DAO itu sendiri Anda memerlukan sesuatu di balik DAO memiliki data dan query berjalan dengan benar.

Hal pertama yang harus dilakukan adalah menggunakan basis data di memori. HyperSQL adalah pilihan yang sangat baik untuk ini karena memiliki kemampuan untuk meniru dialek database lain - sehingga perbedaan kecil antara database tetap sama (tipe data, fungsi dan sejenisnya). hsqldb juga memiliki beberapa fitur bagus untuk pengujian unit.

db.url=jdbc:hsqldb:file:src/test/resources/testData;shutdown=true;

Ini memuat keadaan database (tabel, data awal) dari testDatafile. shutdown=trueakan secara otomatis mematikan basis data ketika koneksi terakhir ditutup.

Menggunakan injeksi ketergantungan , mintalah unit test memilih database yang berbeda dari apa yang digunakan produksi (atau tes, atau lokal).

DAO Anda kemudian menggunakan database yang disuntikkan di mana Anda dapat meluncurkan tes terhadap database.

Tes unit kemudian akan terlihat seperti (banyak hal yang membosankan tidak termasuk untuk singkatnya):

    @Before
    public void setUpDB() {
        DBConnection connection = new DBConnection();
        try {
            conn = connection.getDBConnection();
            insert = conn.prepareStatement("INSERT INTO data (txt, ts, active) VALUES (?, ?, ?)");
        } catch (SQLException e) {
            e.printStackTrace();
            fail("Error instantiating database table: " + e.getMessage());
        }
    }

    @After
    public void tearDown() {
        try {
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void addData(String txt, Timestamp ts, boolean active) throws Exception {
        insert.setString(1, txt);
        insert.setTimestamp(2, ts);
        insert.setBoolean(3, active);
        insert.execute();
    }

    @Test
    public void testGetData() throws Exception {
        // load data
        Calendar time = Calendar.getInstance();
        long now = time.getTimeInMillis();
        long then1h = now - (60 * 60 * 1000);  // one hour ago
        long then2m = now - (60 * 1000 * 2);   // two minutes ago
        addData("active_foo", new Timestamp(then1h), true);     // active but old
        addData("inactive_bar", new Timestamp(then1h), false);  // inactive and old
        addData("active_quz", new Timestamp(then2m), true);     // active and new
        addData("inactive_baz", new Timestamp(then2m), false);  // inactive and new

        DataAccess dao = new DataAccess();
        int count = 0;
        for (Data data : dao.getData()) {
            count++;
            assertTrue(data.getTxt().startsWith("active"));
        }

        assertEquals("got back " + count + " rows instead of 1", count, 1);
    }

Dan dengan demikian, Anda punya tes unit yang memanggil DAO dan menggunakan data yang disiapkan dalam database on the fly yang ada selama durasi tes. Anda tidak perlu khawatir tentang sumber daya eksternal atau keadaan basis data sebelum menjalankan, atau mengembalikan ke kondisi yang diketahui (well, 'kondisi yang dikenal' adalah 'tidak ada' yang sepele untuk dikembalikan ke keadaan semula).

DBUnit dapat membuat banyak dari apa yang saya jelaskan sebagai proses yang lebih sederhana dalam mengatur basis data, membuat tabel dan memuat data. Jika Anda perlu menggunakan database aktual karena suatu alasan, sejauh ini ini adalah alat yang lebih baik untuk digunakan.

Kode di atas adalah bagian dari proyek pakar yang saya tulis untuk membuktikan konsep TestingWithHsqldb di github


sumber
2
Saya tidak tahu tentang bagian mana HSQL dapat mengejek dialek vendor db lain. Terima kasih.
Michael
1
@Dog Ini dapat dilakukan melalui properti basis data seperti sql.syntax_mys=trueyang mengubah cara hsqldb bekerja: "Properti ini, ketika disetel benar, memungkinkan dukungan untuk jenis TEXT dan AUTO_INCREMENT dan juga memungkinkan kompatibilitas dengan beberapa aspek lain dari dialek ini." sementara sql.syntax_ora=truetidak "Properti ini, ketika disetel benar, memungkinkan dukungan untuk jenis non-standar. Ini juga memungkinkan sintaks DUAL, ROWNUM, NEXTVAL dan CURRVAL dan dan juga memungkinkan kompatibilitas dengan beberapa aspek lain dari dialek ini."
DBUnit adalah caranya :)
Silviu Burcea
@ SilviuBurcea DBUnit tentu membuat banyak dari 'plumbing' pengaturan lingkungan pengujian basis data yang kompleks jauh lebih mudah daripada melakukannya dengan tangan. Terkadang masih berguna untuk mengetahui bagaimana melakukannya dengan tangan jika Anda perlu (pendekatan 'dengan tangan' yang disebutkan di atas dapat dimigrasikan ke bahasa lain di mana DBUnit bukan pilihan).
Anda dapat melihat Acolyte
cchantep
2

Pertama, Anda tidak boleh melakukan pengujian di lingkungan produksi. Anda harus memiliki lingkungan pengujian yang mencerminkan lingkungan produksi Anda dan melakukan tes integrasi di sana.

Jika Anda melakukan itu, maka Anda dapat melakukan sejumlah hal.

  • Tulis unit test yang menguji untuk melihat apakah SQL yang sesuai dikirimkan ke item tiruan menggunakan kerangka kerja mengejek seperti Mockito. Ini akan memastikan bahwa metode Anda melakukan apa yang seharusnya dilakukan dan mengeluarkan integrasi dari gambar.
  • Tulis skrip pengujian SQL yang menunjukkan kesesuaian SQL yang Anda uji dalam unit test Anda. Ini dapat membantu dengan masalah penyetelan yang mungkin Anda alami, karena Anda juga dapat menjalankan penjelasan dan semacamnya berdasarkan skrip pengujian Anda.
  • Gunakan DBUnit, seperti yang disebutkan oleh @Sergio.
Matthew Flynn
sumber
Aduh ketika saya mengatakan lingkungan produksi benar-benar saya maksud simulasi itu. Terima kasih atas balasan Anda, saya akan melihat Mockito karena itu adalah sesuatu yang saya ingin pelajari juga.
Michael
1

Pada proyek kami, setiap pengembang menjalankan database kosong, strukturnya sama dengan database produksi.

Di setiap unit tes TestInitialize, kami membuat koneksi & transaksi ke database ditambah beberapa objek default yang kami butuhkan untuk setiap tes. Dan semuanya akan kembali setelah akhir setiap metode atau kelas.

Dengan cara ini, mungkin untuk menguji lapisan sql. Bahkan, setiap permintaan atau panggilan basis data harus diuji dengan cara ini.

Kelemahannya adalah lambat, jadi kami menempatkannya dalam proyek terpisah dari pengujian unit reguler kami. Dimungkinkan untuk mempercepat ini dengan menggunakan basis data dalam memori tetapi idenya tetap sama.

Carra
sumber
Jika menggunakan basis data dalam memori, maka pendekatan drop-create sebelum semua test suite dijalankan dapat digunakan sebagai pengganti transaksi roll-back, yang jauh lebih cepat.
Downhillski
Tidak pernah terpikir untuk melakukan itu sebelumnya. Dalam pengujian kami, sebagian besar tes membuat pengguna 'x' meskipun yang unik. Membuat db sekali berarti mengubah tes untuk menggunakan kembali objek-objek itu.
Carra
Saya tahu, kami berada di halaman yang sama dan saya menyukai pendekatan Anda. pendekatan Anda menjamin setiap kasus uji dapat dijalankan secara independen terlepas dari urutannya, dan setiap kali sebelum dijalankan, kondisi tabel data sama.
Downhillski
Itu benar, urutannya tidak masalah. Kami telah melihat tes gagal sebelumnya karena urutan unit test run berbeda pada build pc kami dan mesin lokal kami.
Carra