Làm thế nào để kiểm tra lớp truy cập dữ liệu?


17

Tôi có một phương thức DAO sử dụng Spring để truy cập JDBC. Nó tính toán tỷ lệ thành công của người bán khi bán một mặt hàng.

Đây là mã:

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);
}

Tôi nên đi thử nghiệm phương pháp này hoặc bất kỳ phương pháp DAO nào với JUnit? Một số thực tiễn tốt nhất để kiểm tra logic truy cập dữ liệu là gì? Tôi đang nghĩ đến việc kiểm tra nó dựa trên cơ sở dữ liệu có thể nhúng được tải với một số dữ liệu, nhưng chúng ta có nên thực hiện các thử nghiệm tích hợp tương tự như môi trường sản xuất về RDBMS và lược đồ không?


Kiểm tra DBUnit . Nó được làm đặc biệt để giải quyết vấn đề của bạn.
Sergio

Câu trả lời:


15

Vấn đề với việc sử dụng cơ sở dữ liệu 'thực' để kiểm tra đơn vị là thiết lập, gỡ xuống và cách ly các bài kiểm tra. Bạn không muốn phải tạo ra một cơ sở dữ liệu MySQL hoàn toàn mới và tạo các bảng và dữ liệu chỉ cho một bài kiểm tra đơn vị. Các vấn đề với điều này phải làm với bản chất bên ngoài của cơ sở dữ liệu và cơ sở dữ liệu kiểm tra của bạn bị hỏng, kiểm tra đơn vị của bạn thất bại. Cũng có vấn đề với việc đảm bảo bạn có một cơ sở dữ liệu duy nhất để thử nghiệm. Họ có thể khắc phục, nhưng có một câu trả lời đơn giản hơn.

Mocking cơ sở dữ liệu là một tùy chọn tuy nhiên nó không kiểm tra các truy vấn thực tế đang chạy. Nó có thể được sử dụng như một giải pháp đơn giản hơn nhiều khi bạn muốn đảm bảo dữ liệu từ DAO đi qua hệ thống đúng cách. Nhưng để kiểm tra DAO, bạn cần một cái gì đó đằng sau DAO có dữ liệu và các truy vấn chạy đúng.

Điều đầu tiên cần làm là sử dụng một cơ sở dữ liệu bộ nhớ. HyperQuery là một lựa chọn tuyệt vời cho việc này vì nó có khả năng mô phỏng phương ngữ của cơ sở dữ liệu khác - do đó sự khác biệt nhỏ giữa các cơ sở dữ liệu vẫn giữ nguyên (kiểu dữ liệu, chức năng và tương tự). hsqldb cũng có một số tính năng hay để thử nghiệm đơn vị.

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

Điều này tải trạng thái của cơ sở dữ liệu (các bảng, dữ liệu ban đầu) từ testDatatệp. shutdown=truesẽ tự động tắt cơ sở dữ liệu khi đóng kết nối cuối cùng.

Sử dụng phép nội xạ phụ thuộc , yêu cầu các bài kiểm tra đơn vị chọn một cơ sở dữ liệu khác với những gì sản xuất (hoặc kiểm tra hoặc cục bộ) xây dựng sử dụng.

DAO của bạn sau đó sử dụng cơ sở dữ liệu được tiêm mà bạn có thể khởi chạy các kiểm tra đối với cơ sở dữ liệu.

Các bài kiểm tra đơn vị sau đó sẽ trông giống như (một loạt các công cụ nhàm chán không được bao gồm cho ngắn gọn):

    @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);
    }

Và do đó, bạn đã có một bài kiểm tra đơn vị gọi DAO và đang sử dụng dữ liệu được thiết lập trong cơ sở dữ liệu đang hoạt động trong suốt thời gian thử nghiệm. Bạn không phải lo lắng về tài nguyên bên ngoài hoặc trạng thái của cơ sở dữ liệu trước khi chạy hoặc khôi phục lại trạng thái đã biết (tốt, 'trạng thái đã biết' là 'không tồn tại', không quan trọng để hoàn nguyên).

DBUnit có thể thực hiện phần lớn những gì tôi đã mô tả một quy trình đơn giản hơn trong việc thiết lập cơ sở dữ liệu, tạo các bảng và tải dữ liệu. Nếu bạn sẽ cần phải sử dụng cơ sở dữ liệu thực tế vì một số lý do, đây là công cụ tốt hơn để sử dụng.

Đoạn mã trên là một phần của dự án maven mà tôi đã viết để chứng minh khái niệm TrialWithHsqldb trên github


2
Tôi không biết về phần mà HSQL có thể chế giễu phương ngữ của nhà cung cấp db khác. Cảm ơn bạn.
Michael

1
@Dog điều này có thể được thực hiện thông qua các thuộc tính cơ sở dữ liệu như sql.syntax_mys=truethay đổi cách hoạt động của hsqldb: "Thuộc tính này, khi được đặt thành đúng, cho phép hỗ trợ các loại văn bản và AUTO_INCREMENT và cũng cho phép tương thích với một số khía cạnh khác của phương ngữ này." trong khi sql.syntax_ora=true"Thuộc tính này, khi được đặt thành đúng, cho phép hỗ trợ cho các loại không chuẩn. Nó cũng cho phép cú pháp DUAL, ROWNUM, NEXTVAL và CURRVAL và cũng cho phép tương thích với một số khía cạnh khác của phương ngữ này."

DBUnit là cách :)
Silviu Burcea

@SilviuBurcea DBUnit chắc chắn làm cho nhiều 'hệ thống ống nước' của việc thiết lập một môi trường kiểm tra cơ sở dữ liệu phức tạp dễ dàng hơn nhiều so với làm bằng tay. Đôi khi vẫn rất hữu ích để biết cách thực hiện bằng tay nếu bạn cần (cách tiếp cận 'bằng tay' được đề cập ở trên có thể được di chuyển sang các ngôn ngữ khác trong đó DBUnit không phải là một tùy chọn).

Bạn có thể xem Acolyte
cchantep

2

Đầu tiên, bạn không bao giờ nên thử nghiệm trong môi trường sản xuất. Bạn nên có một môi trường thử nghiệm phản ánh môi trường sản xuất của bạn và thực hiện các thử nghiệm tích hợp ở đó.

Nếu bạn làm điều đó, thì bạn có thể làm một số điều.

  • Viết các bài kiểm tra đơn vị kiểm tra để xem liệu SQL thích hợp có được gửi đến một mục giả hay không bằng cách sử dụng khung mô phỏng như Mockito. Điều này sẽ đảm bảo rằng phương thức của bạn đang thực hiện những gì nó được cho là phải làm và đưa sự tích hợp ra khỏi bức tranh.
  • Viết các kịch bản SQL kiểm tra thể hiện sự phù hợp của SQL mà bạn đã kiểm tra trong các bài kiểm tra đơn vị của mình. Điều này có thể giúp với bất kỳ vấn đề điều chỉnh nào bạn có thể gặp phải, vì bạn cũng có thể chạy giải thích và như vậy dựa trên các tập lệnh thử nghiệm của bạn.
  • Sử dụng DBUnit, như được đề cập bởi @Sergio.

Thật tệ khi tôi nói môi trường sản xuất thực sự tôi có nghĩa là một mô phỏng của nó. Cảm ơn bạn đã trả lời, tôi sẽ xem Mockito vì đó là điều tôi cũng muốn học.
Michael

1

Trong dự án của chúng tôi, mỗi nhà phát triển đang chạy một cơ sở dữ liệu trống, cấu trúc của nó giống như cơ sở dữ liệu sản xuất.

Trong mỗi bài kiểm tra đơn vị TestInitialize, chúng tôi tạo kết nối & giao dịch đến cơ sở dữ liệu cộng với một số đối tượng mặc định mà chúng tôi cần cho mỗi bài kiểm tra. Và mọi thứ sẽ được khôi phục sau khi kết thúc mỗi phương thức hoặc lớp.

Theo cách này, có thể kiểm tra lớp sql. Trong thực tế, mọi truy vấn hoặc cuộc gọi cơ sở dữ liệu phải được kiểm tra theo cách này.

Nhược điểm là nó chậm, vì vậy chúng tôi đưa nó vào một dự án riêng biệt từ các bài kiểm tra đơn vị thông thường của chúng tôi. Có thể tăng tốc độ này bằng cách sử dụng cơ sở dữ liệu trong bộ nhớ nhưng ý tưởng vẫn giữ nguyên.


Nếu sử dụng cơ sở dữ liệu trong bộ nhớ, thì cách tiếp cận thả xuống trước khi tất cả các bộ kiểm tra chạy có thể được sử dụng thay cho quyền giao dịch quay lại, nhanh hơn rất nhiều.
Downhillski

Chưa bao giờ nghĩ làm theo cách đó trước đây. Trong các thử nghiệm của chúng tôi, hầu hết các thử nghiệm đều tạo ra một người dùng 'x' mặc dù là duy nhất. Tạo một db một lần có nghĩa là thay đổi các bài kiểm tra để sử dụng lại các đối tượng đó.
Carra

Tôi biết, chúng tôi đang ở trên cùng một trang và tôi thích cách tiếp cận của bạn. Cách tiếp cận của bạn đảm bảo mỗi trường hợp kiểm thử có thể được chạy độc lập bất kể thứ tự nào và mỗi lần trước khi nó được chạy, trạng thái của bảng dữ liệu là như nhau.
Downhillski

Đó là chính xác, thứ tự không quan trọng sau đó. Chúng tôi đã thấy các thử nghiệm thất bại trước đây vì thứ tự chạy thử nghiệm đơn vị khác nhau trên máy tính xây dựng và máy cục bộ của chúng tôi.
Carra
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.