Mestrer test: JUnit, Mockito og integrationstest · Lektion

Test af databaseinteraktioner

Skriv integrationstests af datatilgangslag, og sørg for korrekt interaktion med relationelle databaser.

Lektion 3 af 412 trin

Test af databaseinteraktioner er en gratis Mestrer test: JUnit, Mockito og integrationstest-lektion på CoddyKit. Dette er lektion 3 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Mestrer test: JUnit, Mockito og integrationstest, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Mestrer test: JUnit, Mockito og integrationstest-kurset indeholder 4 lektioner i alt.

Hvorfor teste databaseinteraktioner?

Databaseinteraktioner er en kritisk del af de fleste applikationer. Test af disse interaktioner sikrer, at din app gemmer, henter og opdaterer data pålideligt.

  • Dataintegritet: Garanterer, at data gemmes korrekt.
  • Forretningslogik: Verificerer forretningsregler, der vedrører data.
  • Forebyggelse af fejl: Opdager problemer, før de når produktionen.

Udfordringerne ved rigtige databaser

Direkte test mod en rigtig produktionsdatabase kan være vanskelig og føre til upålidelige test:

  • Langsom: Rigtige databaser tilføjer betydelig tid til testsamlinger.
  • Tilstandsbaseret: Test kan efterlade data, som påvirker efterfølgende test.
  • Kompleks opsætning: Kræver en kørende ekstern tjeneste og specifikke konfigurationer.
  • Isolation: Det er vanskeligt at sikre, at hver test kører i et isoleret miljø.

Databaser i hukommelsen til undsætning

For at overvinde disse udfordringer bruger vi ofte databaser i hukommelsen til integrationstest. Det er lette databaser, der kører helt i applikationens hukommelse.

  • Hurtige: Ingen disk-I/O og hurtig opstart og nedlukning.
  • Isolerede: Hver testkørsel kan starte med en ny, tom database.
  • Nem opsætning: Ofte kræves blot en afhængighed og en forbindelses-URL.

Populære valg omfatter H2, HSQLDB og Apache Derby.

Opsætning af din H2-database

Lad os se, hvor nemt det er at oprette forbindelse til en H2-database i hukommelsen ved hjælp af standard-JDBC. Den fungerer ligesom en almindelig database, men ligger i applikationens hukommelse.

Du skal bruge H2-afhængigheden (f.eks. 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"));
      }
    }
  }
}

Introduktion til dataadgangslaget

Din applikation bruger typisk mønsteret Data Access Object (DAO) eller Repository til at interagere med databasen. Dette lag skjuler detaljerne på lavt niveau i JDBC eller ORM.

Vi bruger en simpel ProductRepository til at administrere 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();
}

Implementering af et simpelt Repository

Her er en grundlæggende implementering af vores ProductRepository ved hjælp af rå JDBC. I en rigtig applikation kunne du bruge Spring's 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;
  }
}

Test af Repository – opsætning

Lad os nu skrive et simpelt program til at teste vores JdbcProductRepository. Vi konfigurerer en H2-database i hukommelsen specifikt til denne testkørsel.

Bemærk, hvordan vi opretter en ny databaseforbindelse til vores Repository, så isolation sikres.

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

Kontrol af databasetilstanden

I en rigtig test er det ikke nok bare at udskrive resultatet. Du skal kontrollere, at databasetilstanden svarer til dine forventninger efter en handling. Det bekræfter, at dine DAO-metoder fungerer korrekt.

Lad os tilføje nogle grundlæggende kontroller for at verificere resultaterne.

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

Sikring af rene test med transaktioner

For at opnå mere robust testisolering, især når du bruger rammeværk som Spring, kan du benytte transaktioner.

Ved at annotere dine testmetoder med @Transactional bliver alle ændringer i databasen inden for testen automatisk rullet tilbage, når testen er færdig. Det efterlader databasen i en ren tilstand til den næste test.

  • Automatisk oprydning: Ingen behov for manuelle DELETE-sætninger.
  • Isolation: Hver test kører, som om den var den eneste.
  • Hurtigere: Tilbagerulning er ofte hurtigere end at slette og indsætte data igen.

Bedste praksis for databasetest

Overvej disse råd for at gøre dine databaseintegrationstest effektive og nemme at vedligeholde:

  • Brug databaser i hukommelsen: For at opnå hastighed og isolation.
  • Minimal opsætning: Opret kun de tabeller og data, der er nødvendige for den specifikke test.
  • Transaktionelle test: Rul automatisk ændringer tilbage.
  • Tydelige kontroller: Verificer de forventede data og den forventede tilstand.
  • Fokusér på DAO: Test dataadgangslaget, ikke forretningslogikken her.

Hurtigt tjek: Databasetest

Hvilken af følgende er en primær fordel ved at bruge en database i hukommelsen som H2 til integrationstest?

Opsummering: Test af databaseinteraktioner

I denne lektion lærte vi, hvordan man effektivt tester databaseinteraktioner.

  • Vi forstod udfordringerne ved at teste med rigtige databaser.
  • Vi opdagede databaser i hukommelsen som H2 som en hurtig og isoleret løsning.
  • Vi så, hvordan man programmæssigt opsætter og interagerer med en H2-database.
  • Vi øvede os i at skrive test til et Data Access Object (DAO) for at sikre dataintegritet og korrekt funktion.
  • Vi stiftede bekendtskab med transaktionelle test til automatisk oprydning.

Bliv ved med at øve dig, så din applikations datalag bliver helt robust!

Gratis at komme i gang

Lær Mestrer test: JUnit, Mockito og integrationstest med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
12
Lektioner
48

Ofte stillede spørgsmål

Er lektionen “Test af databaseinteraktioner” gratis?

Ja — alle 3 lektioner i læringssporet Mestrer test: JUnit, Mockito og integrationstest, inklusive “Test af databaseinteraktioner”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Mestrer test: JUnit, Mockito og integrationstest-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Test af databaseinteraktioner”?

Skriv integrationstests af datatilgangslag, og sørg for korrekt interaktion med relationelle databaser. Du øver dig i Mestrer test: JUnit, Mockito og integrationstest med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Mestrer test: JUnit, Mockito og integrationstest?

Der kræves ingen tidligere erfaring. Mestrer test: JUnit, Mockito og integrationstest på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 3 af 4.

Hvor lang tid tager lektionen “Test af databaseinteraktioner”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Mestrer test: JUnit, Mockito og integrationstest-lektion?

Ja. Alle Mestrer test: JUnit, Mockito og integrationstest-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Unit-tests kontra integrationstests
  2. Opsætning af integrationstests
  3. Test af databaseinteraktioner
  4. Test af eksterne API'er med WireMock
← Tilbage til Mestrer test: JUnit, Mockito og integrationstest