Testning på avancerad nivå: JUnit, Mockito och integrationstester · Lektion

Testa databasinteraktioner

Skriv integrationstester för dataåtkomstlager och säkerställ korrekt interaktion med relationsdatabaser.

Lektion 3 av 412 steg

Testa databasinteraktioner är en gratis lektion i Testning på avancerad nivå: JUnit, Mockito och integrationstester på CoddyKit. Detta är lektion 3 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Testning på avancerad nivå: JUnit, Mockito och integrationstester, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Testning på avancerad nivå: JUnit, Mockito och integrationstester innehåller totalt 4 lektioner.

Varför testa databasinteraktioner?

Databasinteraktioner är en viktig del av de flesta applikationer. Genom att testa dessa interaktioner säkerställer ni att appen på ett tillförlitligt sätt lagrar, hämtar och uppdaterar data.

  • Dataintegritet: Garanterar att data lagras korrekt.
  • Affärslogik: Verifierar affärsregler som rör data.
  • Förebyggande av fel: Upptäcker problem innan de når produktion.

Utmaningarna med riktiga databaser

Att testa direkt mot en riktig produktionsdatabas kan vara komplicerat och leda till opålitliga tester:

  • Långsamma: Riktiga databaser lägger till betydande tid i testsviterna.
  • Tillståndsberoende: Tester kan lämna kvar data som påverkar efterföljande tester.
  • Komplex konfiguration: Kräver en körande extern tjänst och specifika inställningar.
  • Isolering: Det är svårt att säkerställa att varje test körs i en isolerad miljö.

Databaser i minnet till undsättning

För att övervinna dessa utmaningar använder vi ofta databaser i minnet för integrationstester. Det är lättviktiga databaser som körs helt i applikationens minne.

  • Snabba: Ingen disk-I/O och snabb start och avstängning.
  • Isolerade: Varje testkörning kan börja med en ny, tom databas.
  • Enkla att konfigurera: Ofta krävs bara ett beroende och en anslutnings-URL.

Populära alternativ är H2, HSQLDB och Apache Derby.

Konfigurera H2-databasen

Låt oss se hur enkelt det är att ansluta till en H2-databas i minnet med standard-JDBC. Den fungerar precis som en vanlig databas, men finns i applikationens minne.

Ni behöver H2-beroendet (till exempel 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 till datalagringsskiktet

Applikationen använder vanligtvis mönstret Data Access Object (DAO) eller Repository för att interagera med databasen. Detta skikt abstraherar bort detaljerna på låg nivå för JDBC eller ORM.

Vi använder en enkel ProductRepository för att hantera Product-objekt.

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

Implementera ett enkelt repository

Här är en grundläggande implementation av vårt ProductRepository med ren JDBC. I en riktig applikation kanske ni använder Springs 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;
  }
}

Testa repositoryt – konfiguration

Nu skriver vi ett enkelt program för att testa vårt JdbcProductRepository. Vi konfigurerar en H2-databas i minnet specifikt för denna testkörning.

Observera hur vi skapar en ny databasanslutning för vårt repository, vilket säkerställer isolering.

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

Verifiera databastillståndet

I ett riktigt test räcker det inte att bara skriva ut resultatet. Ni måste asserta att databastillståndet motsvarar era förväntningar efter en åtgärd. Det bekräftar att DAO-metoderna fungerar korrekt.

Låt oss lägga till några grundläggande kontroller för att verifiera resultaten.

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

Säkerställ rena tester med transaktioner

För mer robust testisolering, särskilt när ni använder ramverk som Spring, kan ni utnyttja transaktioner.

Genom att annotera testmetoderna med @Transactional återställs alla ändringar som görs i databasen under testet automatiskt efter att testet är klart. Då lämnas databasen i ett rent tillstånd inför nästa test.

  • Automatisk rensning: Inga manuella DELETE-satser behövs.
  • Isolering: Varje test körs som om det vore det enda testet.
  • Snabbare: Återställning är ofta snabbare än att ta bort och infoga data igen.

Bästa praxis för databastestning

Överväg följande tips för att göra era databasrelaterade integrationstester effektiva och lättunderhållna:

  • Använd databaser i minnet: För snabbhet och isolering.
  • Minimal konfiguration: Skapa endast de tabeller och data som behövs för det specifika testet.
  • Transaktionella tester: Återställ ändringar automatiskt.
  • Tydliga assertioner: Verifiera förväntade data och tillstånd.
  • Fokusera på DAO: Testa datalagringsskiktet, inte affärslogiken här.

Snabb kontroll: databastestning

Vilken av följande är en huvudsaklig fördel med att använda en databas i minnet, som H2, för integrationstester?

Sammanfattning: testa databasinteraktioner

I den här lektionen lärde vi oss att effektivt testa databasinteraktioner.

  • Vi förstod utmaningarna med att testa riktiga databaser.
  • Vi upptäckte databaser i minnet, som H2, som en snabb och isolerad lösning.
  • Vi såg hur man programmatiskt konfigurerar och interagerar med en H2-databas.
  • Vi övade på att skriva tester för ett Data Access Object (DAO) för att säkerställa dataintegritet och korrekt beteende.
  • Vi berörde transaktionella tester för automatisk rensning.

Fortsätt öva för att säkerställa att applikationens datalager är helt robust!

Gratis att börja

Lär dig Testning på avancerad nivå: JUnit, Mockito och integrationstester med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
12
Lektioner
48

Vanliga frågor

Är lektionen ”Testa databasinteraktioner” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Testning på avancerad nivå: JUnit, Mockito och integrationstester, inklusive ”Testa databasinteraktioner”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Testning på avancerad nivå: JUnit, Mockito och integrationstester innehåller totalt 4 lektioner.

Vad lär jag mig i ”Testa databasinteraktioner”?

Skriv integrationstester för dataåtkomstlager och säkerställ korrekt interaktion med relationsdatabaser. Ni övar på Testning på avancerad nivå: JUnit, Mockito och integrationstester med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Testning på avancerad nivå: JUnit, Mockito och integrationstester?

Du behöver inga förkunskaper. Utbildningen i Testning på avancerad nivå: JUnit, Mockito och integrationstester på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 3 av 4.

Hur lång tid tar lektionen ”Testa databasinteraktioner”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Testning på avancerad nivå: JUnit, Mockito och integrationstester-lektionen?

Ja. Varje Testning på avancerad nivå: JUnit, Mockito och integrationstester-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Enhets- och integrationstester
  2. Konfigurera integrationstester
  3. Testa databasinteraktioner
  4. Testa externa API:er med WireMock
← Tillbaka till Testning på avancerad nivå: JUnit, Mockito och integrationstester