Jak przetestować warstwę dostępu do danych?

17

Mam metodę DAO, która wykorzystuje Spring do dostępu JDBC. Oblicza wskaźnik sukcesu sprzedawcy w sprzedaży przedmiotu.

Oto kod:

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

Jak powinienem przejść do testowania tej metody lub dowolnej metody DAO z JUnit? Jakie są najlepsze praktyki testowania logiki dostępu do danych? Zastanawiam się nad przetestowaniem go na bazie danych, którą można osadzić, załadowanej pewnymi danymi, ale czy nie powinniśmy wykonywać testów integracyjnych podobnych do środowiska produkcyjnego pod względem RDBMS i schematu?

Michael
źródło
Sprawdź DBUnit . Został stworzony specjalnie, aby rozwiązać Twój problem.
Sergio

Odpowiedzi:

15

Problem z użyciem „prawdziwej” bazy danych do testowania jednostkowego polega na konfiguracji, demontażu i izolacji testów. Nie chcesz rozpędzać całkowicie nowej bazy danych MySQL i tworzyć tabele i dane tylko dla jednego testu jednostkowego. Problemy z tym związane są z zewnętrznym charakterem bazy danych, a testowa baza danych jest wyłączona, testy jednostkowe nie powiodły się. Występują również problemy z upewnieniem się, że masz unikalną bazę danych do testowania. Można je pokonać, ale istnieje prostsza odpowiedź.

Wyśmiewanie bazy danych jest jedną z opcji, jednak nie testuje faktycznie uruchomionych zapytań. Może być stosowany jako znacznie prostsze rozwiązanie, gdy chcesz mieć pewność, że dane z DAO przechodzą prawidłowo przez system. Ale do testowania samego DAO potrzebujesz czegoś, za czym DAO ma dane i zapytania działają poprawnie.

Pierwszą rzeczą do zrobienia jest użycie bazy danych w pamięci. HyperSQL jest doskonałym wyborem do tego celu, ponieważ ma możliwość emulacji dialektu innej bazy danych - dzięki czemu drobne różnice między bazami danych pozostają takie same (typy danych, funkcje i tym podobne). hsqldb ma również kilka fajnych funkcji do testowania jednostek.

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

Spowoduje to załadowanie stanu bazy danych (tabel, danych początkowych) z testDatapliku. shutdown=trueautomatycznie zamknie bazę danych po zamknięciu ostatniego połączenia.

Korzystając z wstrzykiwania zależności , niech testy jednostkowe wybierają inną bazę danych niż ta, którą wykorzystują wersje produkcyjna (testowa lub lokalna).

Następnie DAO korzysta z wstrzykniętej bazy danych, dla której można uruchomić testy względem bazy danych.

Testy jednostkowe będą wtedy wyglądały następująco (kilka nudnych rzeczy nie jest zawartych dla zwięzłości):

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

I tak masz test jednostkowy, który wywołuje DAO i korzysta z danych skonfigurowanych w bazie danych w locie, która istnieje przez czas trwania testu. Nie musisz martwić się o zasoby zewnętrzne lub stan bazy danych przed uruchomieniem lub przywrócenie do znanego stanu (cóż, „znany stan” to „nie istnieje”, do którego przywrócenie jest banalne).

DBUnit może znacznie uprościć to, co opisałem, w prostszym procesie konfigurowania bazy danych, tworzenia tabel i ładowania danych. Jeśli z jakiegoś powodu będziesz musiał korzystać z rzeczywistej bazy danych, jest to zdecydowanie lepsze narzędzie do użycia.

Powyższy kod jest częścią projektu maven, który napisałem dla potwierdzenia koncepcji TestingWithHsqldb na github


źródło
2
Nie wiedziałem o tym, że HSQL może wyśmiewać dialekt innego dostawcy db. Dziękuję Ci.
Michael
1
@Dog można tego dokonać za pomocą właściwości bazy danych, takich jak sql.syntax_mys=truezmiana sposobu działania hsqldb: „Ta właściwość, jeśli jest ustawiona na true, umożliwia obsługę typów TEKST i AUTO_INCREMENT, a także umożliwia zgodność z niektórymi innymi aspektami tego dialektu”. while sql.syntax_ora=truerobi „Ta właściwość, jeśli jest ustawiona na true, umożliwia obsługę niestandardowych typów. Umożliwia także składnię DUAL, ROWNUM, NEXTVAL i CURRVAL, a także umożliwia zgodność z niektórymi innymi aspektami tego dialektu”.
DBUnit jest drogą :)
Silviu Burcea
@SilviuBurcea DBUnit z pewnością znacznie ułatwia „instalację wodociągową” związaną z konfigurowaniem złożonego środowiska testowania bazy danych niż wykonywanie go ręcznie. Wciąż czasem warto wiedzieć, jak to zrobić ręcznie, jeśli zajdzie taka potrzeba (wspomniane wyżej podejście „ręcznie” można przenieść na inne języki, w których DBUnit nie jest opcją).
Możesz
rzucić
2

Po pierwsze, nigdy nie należy przeprowadzać testów w środowisku produkcyjnym. Powinieneś mieć środowisko testowe, które odzwierciedla środowisko produkcyjne i przeprowadzać tam testy integracyjne.

Jeśli to zrobisz, możesz zrobić wiele rzeczy.

  • Napisz testy jednostkowe, które sprawdzają, czy odpowiedni element SQL jest przesyłany do elementu próbnego przy użyciu frameworku, takiego jak Mockito. Zapewni to, że twoja metoda robi to, co powinna i usunie integrację z obrazu.
  • Napisz testowe skrypty SQL demonstrujące odpowiedniość kodu SQL, który testowałeś w testach jednostkowych. Może to pomóc we wszelkich problemach ze strojeniem, jakie możesz napotkać, ponieważ możesz także uruchomić wyjaśnienia i takie na podstawie skryptów testowych.
  • Użyj DBUnit, jak wspomniano w @Sergio.
Matthew Flynn
źródło
Wow, kiedy powiedziałem, że środowisko produkcyjne naprawdę mam na myśli jego symulację. Dziękuję za odpowiedź. Rzucę okiem na Mockito, ponieważ tego też chciałem się nauczyć.
Michael
1

W naszym projekcie każdy programista prowadzi pustą bazę danych, jej struktura jest taka sama jak produkcyjna baza danych.

W każdym teście jednostkowym TestInitialize tworzymy połączenie i transakcję z bazą danych oraz niektóre domyślne obiekty potrzebne do każdego testu. I wszystko jest wycofywane po zakończeniu każdej metody lub klasy.

W ten sposób można przetestować warstwę SQL. W rzeczywistości każde zapytanie lub wywołanie bazy danych musi zostać przetestowane w ten sposób.

Minusem jest to, że jest powolny, dlatego umieszczamy go w innym projekcie niż nasze regularne testy jednostkowe. Można to przyspieszyć za pomocą bazy danych w pamięci, ale pomysł pozostaje ten sam.

Carra
źródło
Jeśli korzystasz z bazy danych w pamięci, to zamiast tworzenia wycofania transakcji można zastosować metodę tworzenia zrzutu przed uruchomieniem wszystkich pakietów testowych, co jest po prostu znacznie szybsze.
Downhillski
Nigdy wcześniej nie myślałem, aby zrobić to w ten sposób. W naszych testach większość testów tworzy użytkownika „x”, co jest unikalne. Jednokrotne utworzenie bazy danych oznaczałoby zmianę testów w celu ponownego wykorzystania tych obiektów.
Carra,
Wiem, jesteśmy na tej samej stronie i podoba mi się twoje podejście. Twoje podejście gwarantuje, że każdy przypadek testowy może być niezależnie uruchamiany niezależnie od kolejności, a za każdym razem przed uruchomieniem stan tabeli danych jest taki sam.
Downhillski,
To prawda, wtedy kolejność nie ma znaczenia. Wcześniej widzieliśmy, że testy kończyły się niepowodzeniem, ponieważ kolejność uruchamiania testów jednostkowych jest inna na naszym komputerze kompilacji i na naszej lokalnej maszynie.
Carra,