Testing i praksis: JUnit, Mockito og integrasjonstester · leksjon

Testing av databaseinteraksjoner

Skriv integrasjonstester for datatilgangslag, og sørg for korrekt samhandling med relasjonsdatabaser.

Leksjon 3 av 412 trinn

Testing av databaseinteraksjoner er en gratis leksjon i Testing i praksis: JUnit, Mockito og integrasjonstester på CoddyKit. Dette er leksjon 3 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Testing i praksis: JUnit, Mockito og integrasjonstester, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Testing i praksis: JUnit, Mockito og integrasjonstester inneholder totalt 4 leksjoner.

Hvorfor teste databasesamhandling?

Databasesamhandling er en viktig del av de fleste applikasjoner. Testing av denne samhandlingen sikrer at appen lagrer, henter og oppdaterer data pålitelig.

  • Dataintegritet: Garanterer at data lagres korrekt.
  • Forretningslogikk: Verifiserer forretningsregler knyttet til data.
  • Feilforebygging: Oppdager problemer før de når produksjonsmiljøet.

Utfordringene med ekte databaser

Direkte testing mot en ekte produksjonsdatabase kan være krevende og føre til upålitelige tester:

  • Treg: Ekte databaser gjør testkjøringene betydelig lengre.
  • Tilstandsfull: Tester kan etterlate data som påvirker påfølgende tester.
  • Komplekst oppsett: Krever en kjørende ekstern tjeneste og bestemte konfigurasjoner.
  • Isolasjon: Det er vanskelig å sikre at hver test kjører i et isolert miljø.

Databaser i minnet til unnsetning

For å overvinne disse utfordringene bruker vi ofte databaser i minnet til integrasjonstester. Dette er lette databaser som kjører fullstendig i minnet til applikasjonen.

  • Raske: Ingen disk-IO og rask oppstart og avslutning.
  • Isolerte: Hver testkjøring kan starte med en ny, tom database.
  • Enkelt oppsett: Ofte trenger du bare en avhengighet og en tilkoblings-URL.

Populære alternativer er H2, HSQLDB og Apache Derby.

Konfigurer H2-databasen

La oss se hvor enkelt det er å koble til en H2-database i minnet ved hjelp av standard JDBC. Den fungerer akkurat som en vanlig database, men ligger i minnet til applikasjonen.

Du trenger H2-avhengigheten (for eksempel Maven: com.h2database:h2).

import java.sql.*;

public class H2Demo {
  public static void main(String[] args) throws SQLException {
    String jdbcUrl = "jdbc:h2:mem:testdb";
    String username = "sa";
    String password = "";

    try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password)) {
      Statement stmt = conn.createStatement();
      stmt.execute("CREATE TABLE products (id INT PRIMARY KEY, name VARCHAR(255))");
      stmt.executeUpdate("INSERT INTO products (id, name) VALUES (1, 'Laptop')");

      ResultSet rs = stmt.executeQuery("SELECT * FROM products");
      if (rs.next()) {
        System.out.println("Product: " + rs.getString("name"));
      }
    }
  }
}

Introduksjon til datatilgangslaget

Applikasjonen din bruker vanligvis mønsteret Data Access Object (DAO) eller Repository for å samhandle med databasen. Dette laget abstraherer bort detaljer på lavt nivå i JDBC eller ORM.

Vi bruker en enkel ProductRepository til å håndtere Product-objekter.

public class Product {
  private int id;
  private String name;

  public Product(int id, String name) {
    this.id = id;
    this.name = name;
  }

  public int getId() { return id; }
  public String getName() { return name; }
}

import java.util.List;

public interface ProductRepository {
  void save(Product product);
  Product findById(int id);
  List<Product> findAll();
}

Implementer et enkelt repository

Her er en grunnleggende implementasjon av ProductRepository ved hjelp av rå JDBC. I en virkelig applikasjon kan du bruke Spring sin JdbcTemplate eller en ORM som Hibernate.

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

// Product class and ProductRepository interface as defined previously

public class JdbcProductRepository implements ProductRepository {
  private Connection conn;

  public JdbcProductRepository(Connection conn) {
    this.conn = conn;
    try (Statement stmt = conn.createStatement()) {
      stmt.execute("CREATE TABLE IF NOT EXISTS products (id INT PRIMARY KEY, name VARCHAR(255))");
    } catch (SQLException e) { throw new RuntimeException(e); }
  }

  @Override
  public void save(Product product) {
    String sql = "INSERT INTO products (id, name) VALUES (?, ?)";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
      ps.setInt(1, product.getId());
      ps.setString(2, product.getName());
      ps.executeUpdate();
    } catch (SQLException e) { throw new RuntimeException(e); }
  }

  @Override
  public Product findById(int id) {
    String sql = "SELECT id, name FROM products WHERE id = ?";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
      ps.setInt(1, id); ResultSet rs = ps.executeQuery();
      if (rs.next()) { return new Product(rs.getInt("id"), rs.getString("name")); }
    } catch (SQLException e) { throw new RuntimeException(e); }
    return null;
  }

  @Override
  public List<Product> findAll() {
    List<Product> products = new ArrayList<>();
    String sql = "SELECT id, name FROM products";
    try (Statement stmt = conn.createStatement()) {
      ResultSet rs = stmt.executeQuery(sql);
      while (rs.next()) {
        products.add(new Product(rs.getInt("id"), rs.getString("name")));
      }
    } catch (SQLException e) { throw new RuntimeException(e); }
    return products;
  }
}

Teste repositoryet – oppsett

La oss nå skrive et enkelt program for å teste JdbcProductRepository. Vi konfigurerer en H2-database i minnet spesielt for denne testkjøringen.

Legg merke til hvordan vi oppretter en ny databasetilkobling for repositoryet, slik at vi sikrer isolasjon.

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

// Product class (simplified for mobile)
class Product {
  private int id; private String name;
  public Product(int id, String name) { this.id = id; this.name = name; }
  public int getId() { return id; }
  public String getName() { return name; }
}

// ProductRepository interface
interface ProductRepository {
  void save(Product product);
  Product findById(int id);
  List<Product> findAll();
}

// JdbcProductRepository (simplified for mobile)
class JdbcProductRepository implements ProductRepository {
  private Connection conn;
  public JdbcProductRepository(Connection conn) {
    this.conn = conn;
    try (Statement stmt = conn.createStatement()) {
      stmt.execute("CREATE TABLE IF NOT EXISTS products (id INT PRIMARY KEY, name VARCHAR(255))");
    } catch (SQLException e) { throw new RuntimeException(e); }
  }
  @Override public void save(Product product) {
    String sql = "INSERT INTO products (id, name) VALUES (?, ?)";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
      ps.setInt(1, product.getId()); ps.setString(2, product.getName());
      ps.executeUpdate();
    } catch (SQLException e) { throw new RuntimeException(e); }
  }
  @Override public Product findById(int id) { /* ... omitted ... */ return null; }
  @Override public List<Product> findAll() { /* ... omitted ... */ return new ArrayList<>(); }
}

public class ProductRepositoryTestRunner {
  public static void main(String[] args) throws SQLException {
    String jdbcUrl = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
    try (Connection conn = DriverManager.getConnection(jdbcUrl, "sa", "")) {
      System.out.println("DB connection established.");
      ProductRepository repository = new JdbcProductRepository(conn);

      Product laptop = new Product(1, "Laptop");
      repository.save(laptop);
      System.out.println("Saved product: " + laptop.getName());

      Product foundProduct = repository.findById(1);
      System.out.println("Found product: " + (foundProduct != null ? foundProduct.getName() : "None"));

      List<Product> allProducts = repository.findAll();
      System.out.println("Total products: " + allProducts.size());
    }
  }
}

Kontroller databasetilstanden

I en virkelig test er det ikke nok å bare skrive ut resultatet. Du må assert at databasetilstanden samsvarer med forventningene dine etter en operasjon. Dette bekrefter at DAO-metodene fungerer korrekt.

La oss legge til noen grunnleggende kontroller for å verifisere resultatene.

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

// Product class (simplified for mobile)
class Product {
  private int id; private String name;
  public Product(int id, String name) { this.id = id; this.name = name; }
  public int getId() { return id; }
  public String getName() { return name; }
}

// ProductRepository interface
interface ProductRepository {
  void save(Product product); Product findById(int id); List<Product> findAll();
}

// JdbcProductRepository (simplified for mobile)
class JdbcProductRepository implements ProductRepository {
  private Connection conn;
  public JdbcProductRepository(Connection conn) { /* ... omitted ... */ this.conn = conn; }
  @Override public void save(Product product) { /* ... omitted ... */ }
  @Override public Product findById(int id) {
    String sql = "SELECT id, name FROM products WHERE id = ?";
    try (PreparedStatement ps = conn.prepareStatement(sql)) {
      ps.setInt(1, id); ResultSet rs = ps.executeQuery();
      if (rs.next()) { return new Product(rs.getInt("id"), rs.getString("name")); }
    } catch (SQLException e) { throw new RuntimeException(e); }
    return null;
  }
  @Override public List<Product> findAll() {
    List<Product> products = new ArrayList<>();
    String sql = "SELECT id, name FROM products";
    try (Statement stmt = conn.createStatement()) {
      ResultSet rs = stmt.executeQuery(sql);
      while (rs.next()) { products.add(new Product(rs.getInt("id"), rs.getString("name"))); }
    } catch (SQLException e) { throw new RuntimeException(e); }
    return products;
  }
}

public class ProductRepositoryAssertions {
  public static void main(String[] args) throws SQLException {
    String jdbcUrl = "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1";
    try (Connection conn = DriverManager.getConnection(jdbcUrl, "sa", "")) {
      ProductRepository repository = new JdbcProductRepository(conn);

      Product laptop = new Product(1, "Laptop");
      repository.save(laptop);

      // Assertion 1: Check if product was saved
      Product found = repository.findById(1);
      if (found != null && found.getName().equals("Laptop")) {
        System.out.println("Test Passed: Product saved & found.");
      } else { System.out.println("Test Failed: Product not found or name mismatch."); }

      // Assertion 2: Check total count
      List<Product> all = repository.findAll();
      if (all.size() == 1) {
        System.out.println("Test Passed: Correct product count.");
      } else { System.out.println("Test Failed: Incorrect product count."); }
    }
  }
}

Sikre rene tester med transaksjoner

For mer robust testisolasjon, særlig når du bruker rammeverk som Spring, kan du benytte transaksjoner.

Ved å annotere testmetodene med @Transactional blir alle endringer som gjøres i databasen under testen, automatisk rullet tilbake når testen er ferdig. Dermed er databasen ren for neste test.

  • Automatisk opprydding: Du trenger ikke manuelle DELETE-setninger.
  • Isolasjon: Hver test kjører som om den var den eneste.
  • Raskere: Tilbakerulling er ofte raskere enn å slette og sette inn data på nytt.

Beste praksis for databasetesting

Ta hensyn til disse tipsene for å gjøre databasens integrasjonstester effektive og enkle å vedlikeholde:

  • Bruk databaser i minnet: For hastighet og isolasjon.
  • Minimalt oppsett: Opprett bare tabeller og data som er nødvendige for den aktuelle testen.
  • Transaksjonstester: Rull automatisk tilbake endringer.
  • Tydelige assertions: Verifiser forventede data og tilstander.
  • Fokuser på DAO: Test datatilgangslaget, ikke forretningslogikken her.

Kort sjekk: databasetesting

Hva er den viktigste fordelen ved å bruke en database i minnet, for eksempel H2, til integrasjonstester?

Oppsummering: testing av databasesamhandling

I denne leksjonen lærte vi hvordan vi effektivt tester databasesamhandling.

  • Vi forsto utfordringene ved å teste med ekte databaser.
  • Vi oppdaget databaser i minnet, som H2, som en rask og isolert løsning.
  • Vi så hvordan vi kan konfigurere og samhandle med en H2-database programmatisk.
  • Vi øvde på å skrive tester for et Data Access Object (DAO) for å sikre dataintegritet og korrekt oppførsel.
  • Vi var innom transaksjonstester for automatisk opprydding.

Fortsett å øve for å sikre at datatilgangslaget i applikasjonen din er bunnsolid!

Gratis å komme i gang

Lær deg Testing i praksis: JUnit, Mockito og integrasjonstester med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
12
Leksjoner
48

Ofte stilte spørsmål

Er leksjonen «Testing av databaseinteraksjoner» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Testing i praksis: JUnit, Mockito og integrasjonstester, inkludert «Testing av databaseinteraksjoner», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Testing i praksis: JUnit, Mockito og integrasjonstester inneholder totalt 4 leksjoner.

Hva lærer jeg i «Testing av databaseinteraksjoner»?

Skriv integrasjonstester for datatilgangslag, og sørg for korrekt samhandling med relasjonsdatabaser. Du øver på Testing i praksis: JUnit, Mockito og integrasjonstester med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Testing i praksis: JUnit, Mockito og integrasjonstester?

Ingen tidligere erfaring er nødvendig. Testing i praksis: JUnit, Mockito og integrasjonstester på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.

Hvor lang tid tar leksjonen «Testing av databaseinteraksjoner»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Testing i praksis: JUnit, Mockito og integrasjonstester-leksjonen?

Ja. Alle Testing i praksis: JUnit, Mockito og integrasjonstester-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Enhetstester kontra integrasjonstester
  2. Klargjøring av integrasjonstester
  3. Testing av databaseinteraksjoner
  4. Testing av eksterne API-er med WireMock
← Tilbake til Testing i praksis: JUnit, Mockito og integrasjonstester