Pruebas de integración con Testcontainers, ejemplo de JPA con la base de datos PostgreSQL

Escrito por picodotdev el .
java planeta-codigo
Enlace permanente Comentarios

Algunas partes del código no es posibles probarlas con teses unitarios ya que tienen dependencias como una base de datos. En estos casos es necesario realizar un test de integración, la dificultad reside en tener esta dependencia en el entorno de pruebas. La herramienta Testcontainers permite iniciar un contenedor Docker con la dependencia cuando el test se ejecuta.

Las pruebas automatizadas permiten validar que el comportamiento del software es el esperado. Permiten verificar que ante cambios no se introducen errores, generan mayor confianza en que el software funciona como se espera y evitan la necesidad de realizar pruebas manuales lentas y repetitivas.

Las pruebas más numerosas son las unitarias que prueban un componente del software de forma aislada de sus dependencias sin invocar operaciones de red. Las dependencias del componente bajo la prueba no son las reales, las dependencias simulan el comportamiento de los componentes a los que sustituyen por stubs o mocks para analizar cómo se comporta el sujeto bajo la prueba o SUT. En el artículo qué es un SUT, fake, stub, mock y spy describo este tipo de objetos que sustituyen a las dependencias reales.

El objetivo de sustituir una dependencia real por un doble es programar el comportamiento del doble, ejecutar la prueba de forma aislada y de forma rápida. Una dependencia real es una base de datos como PostgreSQL, NoSQL como MongoDB o un servicio que requiere comunicación por red como GraphQL, al sustituir las dependencias no son necesarias en el entorno de la prueba lo que lo hace más sencillo.

Sin embargo, el código a ejecutar en la aplicación finalmente hace uso de las dependencias reales, y estas no se prueban en las pruebas unitarias, lo que significa los mismos problemas de errores y pruebas manuales de un código que no tiene pruebas automatizadas o pruebas que se basan en dobles que sustituyen a los reales y en algunos casos quiza se comporten de forma diferente. Las pruebas de integración permiten probar el funcionamiento de dos componentes relacionados.

Testcontainers es una herramienta que permite realizar pruebas de integración utilizando las mismas dependencias que usa la aplicación en su funcionamiento normal y disponer de estas dependencias en el entorno de prueba. Si usa una base de datos PostgreSQL las pruebas usan esta base de datos, lo mismo si las pruebas necesitan, MongoDB, RabbitMQ o Redis. Testcontainers es una tecnología Java que se basa en el uso de contenedores Docker para las pruebas. Al iniciar las pruebas de integración Testcontainers se encarga de iniciar un contenedor efímero por cada una de las dependencias que se necesite, al finalizar las pruebas el contenedor es destruido.

Una aplicación que use una base de datos SQL lanza consultas SQL, aunque el lenguaje de consulta SQL está estandarizado las bases de datos incluyen diferencias en las funciones, sintaxis y palabras clave específicas de esa base de datos que no son compatibles con otras bases de datos. El caso es que probar esas consultas contra una base de datos en memoria puede hacer que el test funcione pero sobre la base de datos real no, la aplicación tiene consultas con condiciones complejas o procedimientos almacenados que es necesario que tengan pruebas, para garantizar mayor fiabilidad de las pruebas es mejor usar la base de datos real y no otra base de datos en memoria como H2, HSQL que a veces se utilizan por no disponer en el entorno de pruebas la base de datos real.

Las partes del código que se puedan probar con teses unitarios es mejor probarlas con este tipo de teses ya que se ejecutan más rápidamente, son más fiables y no necesitan dependencias. Si ciertas partes del código no se pueden probar con una prueba unitaria por tener dependencias las pruebas de integración son la opción aconsejada para que también tengan sus correspondientes teses.

En el siguiente ejemplo muestro una clase repositorio que accede a una base de datos PostgreSQL implementado con Spring Data con JPA e Hibernate que en su ejecución lanza consultas SQL a la base de datos relacional. Para probar su comportamiento en una prueba de integración se usa Testcontainers que arranca el contenedor Docker de PostgreSQL. La prueba está implementada con JUnit 5 y la aplicación hace uso de Spring Boot. La prueba de integración realiza un par de pruebas para esa clase repositorio insertando los datos de prueba de dos formas diferentes.

Esta es la entidad de Hibernate que la clase repositorio persiste en la base de datos.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package io.github.picodotdev.blogbitix.testcontainers;

import java.util.Objects;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "Person")
public class Person {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null)
            return false;
        if (!(o instanceof Person))
            return false;

        Person that = (Person) o;
        return super.equals(that)
            && Objects.equals(this.id, that.id);
    }
}
Person.java

La clase repositorio es una implementación para el acceso a la base de datos haciendo uso de las facilidades que proporciona Spring Data. La interfaz CrudRepository ofrece métodos con las oepraciones de lectura, guardado, actualización y eliminación básicas que en su invocación generan las consultas SQL select, insert, update y delete correspondientes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package io.github.picodotdev.blogbitix.testcontainers;

import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;

public interface PersonRepository extends CrudRepository<Person, Long> {

    @Override
    @Modifying
    @Query("delete from Person")
    void deleteAll();
}
PersonRepository.java

Testcontainers necesita iniciar contenedores para lo que es necesario instalar previamente según la guía para Docker este software de contenedores. En archivo de construcción es necesario incluir las dependencias de Testcontainers, de Spring y el controlador para la base de datos PostgreSQL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
plugins {
    id 'java'
    id 'application'
}

group = 'io.github.picodotdev.blogbitix.testcontainers'
version = '1.0'

java {
    sourceCompatibility = JavaVersion.VERSION_11
}

application {
    mainClass = 'io.github.picodotdev.blogbitix.testcontainers.Main'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation platform('org.springframework.boot:spring-boot-dependencies:2.3.0.RELEASE')

    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.liquibase:liquibase-core'

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.testcontainers:testcontainers:1.14.3'
    testImplementation 'org.testcontainers:junit-jupiter:1.14.3'
    testImplementation 'org.testcontainers:postgresql:1.14.3'

    testImplementation 'org.postgresql:postgresql:42.2.12'
    testImplementation 'redis.clients:jedis:3.3.0'
}

test {
    useJUnitPlatform()
}
build.gradle

La clase DefaultPostgresContainer permite encapsular el inicio del contenedor para PostgresSQL y configurar las variables spring.datasource.url, spring.datasource.username y spring.datasource.password con la URL de conexión, usuario y contraseña antes de que el contexto de Spring se inicie. La clase DefaultPostgresContainer permite reutilizar esta conguración en diferentes teses y hacer uso de ella donde sea necesario con la anotación ContextConfiguration.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package io.github.picodotdev.blogbitix.testcontainers;

import org.springframework.boot.test.util.TestPropertyValues;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

import org.testcontainers.containers.PostgreSQLContainer;

public class DefaultPostgresContainer extends PostgreSQLContainer<DefaultPostgresContainer> {

    private static final String IMAGE_VERSION = "postgres:12";
    private static DefaultPostgresContainer container;

    private DefaultPostgresContainer() {
        super(IMAGE_VERSION);
    }

    public static DefaultPostgresContainer getInstance() {
        if (container == null) {
            container = new DefaultPostgresContainer();
        }
        return container;
    }

    @Override
    public void start() {
        super.start();
    }
 
    @Override
    public void stop() {
        super.stop();
    }

    public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            DefaultPostgresContainer container = DefaultPostgresContainer.getInstance();
            container.start();
            TestPropertyValues.of(
                    "spring.datasource.url=" + container.getJdbcUrl(),
                    "spring.datasource.username=" + container.getUsername(),
                    "spring.datasource.password=" + container.getPassword()
            ).applyTo(configurableApplicationContext.getEnvironment());
        }
    }
}
DefaultPostgresContainer.java

Se puede iniciar cualquier contenedor de forma genérica con el siguiente código, indicando la imagen del contenedor y etiqueta además del puerto que expone. Testcontainer los expone de forma local usando un puerto aleatorio, se necesita el host y puerto que permite la conexión al servicio obtenidos de la referencia del contenedor. En este caso se inicia un contenedor Redis accedido con la librería Jedis.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package io.github.picodotdev.blogbitix.testcontainers;

import org.junit.Assert;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import redis.clients.jedis.Jedis;

@SpringBootTest
@Testcontainers
@ContextConfiguration(initializers = { RedisTest.Initializer.class })
public class RedisTest {

    @Container
    private GenericContainer redis = new GenericContainer<>("redis:6").withExposedPorts(6379);

    private Jedis jedis;

    @BeforeEach
    void beforeEach() {
        String host = redis.getHost();
        Integer port = redis.getFirstMappedPort();

        jedis = new Jedis(host, port);
    }

    @Test
    void redisTest() {
        jedis.set("foo", "bar");

        Assert.assertEquals("bar", jedis.get("foo"));
    }

    public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
            TestPropertyValues.of("spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration")
                    .applyTo(configurableApplicationContext.getEnvironment());
        }
    }
}
RedisTest.java

La prueba está implementada con JUnit 5 y Spring Boot, con la anotación ContextConfiguration se indica a JUnit y a Spring que inicie el contenedor de PostgreSQL antes de iniciar el contexto de Spring que configura las variables de conexión a la base de datos y antes de ejecutar los métodos de prueba. Los métodos de prueba son muy sencillos simplemente persisten en la base de datos varias entidades y se prueba que el número de entidades presentes en la base de datos al contarlas es el esperado.

Los datos iniciales de prueba o fixture se insertan de dos formas diferentes en cada método de prueba, en uno haciendo uso de la propia clase repositorio y en otro con la anotación Sql que contiene las sentencias SQL de inserción equivalentes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package io.github.picodotdev.blogbitix.testcontainers;

import java.util.List;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.ContextConfiguration;

import org.springframework.beans.factory.annotation.Autowired;

@SpringBootTest
@ContextConfiguration(initializers = { DefaultPostgresContainer.Initializer.class })
public class PersonRepositoryTest {

    @Autowired
    private PersonRepository personRepository;

    @AfterEach
    void afterEach() {
        personRepository.deleteAll();
    }

    @Test
    void repositoryPersonCount() {
        // given
        List<Person> persons = List.of(new Person("James Gosling"), new Person("Linus Torvalds"), new Person("Richard Stallman"), new Person("Bill Gates"), new Person("Steve Jobs"), new Person("Dennis Ritchie"));
        personRepository.saveAll(persons);

        // then
        assertEquals(persons.size(), personRepository.count());
    }

    @Test
    @Sql("/sql/persons.sql")
    void sqlPersonCount() {
        // then
        assertEquals(6, personRepository.count());
    }
}
PersonRepositoryTest.java
1
2
3
4
5
6
insert into Person (id, name) values (1, 'James Gosling');
insert into Person (id, name) values (2, 'Linus Torvalds');
insert into Person (id, name) values (3, 'Richard Stallman');
insert into Person (id, name) values (4, 'Bill Gates');
insert into Person (id, name) values (5, 'Steve Jobs');
insert into Person (id, name) values (6, 'Dennis Ritchie');
persons.sql

En la salida de trazas de los teses se observa como Testcontainers inicia el contenedor PostgreSQL y como los teses generan las sentencias SQL de insert, count y delete para eliminar los datos del fixture.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

2020-06-07 09:49:25.003  INFO 4244 --- [    Test worker] tAndSystemPropertyClientProviderStrategy : Found docker client settings from environment
2020-06-07 09:49:25.004  INFO 4244 --- [    Test worker] o.t.d.DockerClientProviderStrategy       : Found Docker environment with Environment variables, system properties and defaults. Resolved dockerHost=unix:///var/run/docker.sock
2020-06-07 09:49:25.120  INFO 4244 --- [    Test worker] org.testcontainers.DockerClientFactory   : Docker host IP address is localhost
2020-06-07 09:49:25.140  INFO 4244 --- [    Test worker] org.testcontainers.DockerClientFactory   : Connected to docker: 
  Server Version: 19.03.11-ce
  API Version: 1.40
  Operating System: Arch Linux
  Total Memory: 31986 MB
...
2020-06-07 09:49:27.387  INFO 4244 --- [tream-916764928] org.testcontainers.DockerClientFactory   : Starting to pull image
2020-06-07 09:49:27.430  INFO 4244 --- [tream-916764928] org.testcontainers.DockerClientFactory   : Pulling image layers:  0 pending,  0 downloaded,  0 extracted, (0 bytes/0 bytes)
2020-06-07 09:49:28.813  INFO 4244 --- [tream-916764928] org.testcontainers.DockerClientFactory   : Pulling image layers:  2 pending,  1 downloaded,  0 extracted, (486 KB/? MB)
2020-06-07 09:49:32.147  INFO 4244 --- [tream-916764928] org.testcontainers.DockerClientFactory   : Pulling image layers:  1 pending,  2 downloaded,  0 extracted, (4 MB/? MB)
2020-06-07 09:49:32.600  INFO 4244 --- [tream-916764928] org.testcontainers.DockerClientFactory   : Pulling image layers:  0 pending,  3 downloaded,  0 extracted, (5 MB/5 MB)
2020-06-07 09:49:32.784  INFO 4244 --- [tream-916764928] org.testcontainers.DockerClientFactory   : Pulling image layers:  0 pending,  3 downloaded,  1 extracted, (5 MB/5 MB)
2020-06-07 09:49:32.869  INFO 4244 --- [tream-916764928] org.testcontainers.DockerClientFactory   : Pulling image layers:  0 pending,  3 downloaded,  2 extracted, (5 MB/5 MB)
2020-06-07 09:49:33.006  INFO 4244 --- [tream-916764928] org.testcontainers.DockerClientFactory   : Pulling image layers:  0 pending,  3 downloaded,  3 extracted, (5 MB/5 MB)
2020-06-07 09:49:33.027  INFO 4244 --- [tream-916764928] org.testcontainers.DockerClientFactory   : Pull complete. 3 layers, pulled in 5s (downloaded 5 MB at 1 MB/s)
2020-06-07 09:49:33.785  INFO 4244 --- [    Test worker] org.testcontainers.DockerClientFactory   : Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2020-06-07 09:49:33.785  INFO 4244 --- [    Test worker] org.testcontainers.DockerClientFactory   : Checking the system...
2020-06-07 09:49:33.786  INFO 4244 --- [    Test worker] org.testcontainers.DockerClientFactory   : ✔︎ Docker server version should be at least 1.6.0
2020-06-07 09:49:34.060  INFO 4244 --- [    Test worker] org.testcontainers.DockerClientFactory   : ✔︎ Docker environment should have more than 2GB free disk space
2020-06-07 09:49:34.070  INFO 4244 --- [    Test worker] 🐳 [postgres:12]                         : Pulling docker image: postgres:12. Please be patient; this may take some time but only needs to be done once.
2020-06-07 09:49:36.059  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Starting to pull image
2020-06-07 09:49:36.064  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  0 pending,  0 downloaded,  0 extracted, (0 bytes/0 bytes)
2020-06-07 09:49:37.729  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers: 13 pending,  1 downloaded,  0 extracted, (1 KB/? MB)
2020-06-07 09:49:44.773  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers: 12 pending,  2 downloaded,  0 extracted, (9 MB/? MB)
2020-06-07 09:49:46.205  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers: 11 pending,  3 downloaded,  0 extracted, (10 MB/? MB)
2020-06-07 09:49:49.709  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers: 10 pending,  4 downloaded,  0 extracted, (14 MB/? MB)
2020-06-07 09:49:50.684  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  9 pending,  5 downloaded,  0 extracted, (16 MB/? MB)
2020-06-07 09:49:51.585  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  8 pending,  6 downloaded,  0 extracted, (17 MB/? MB)
2020-06-07 09:49:57.752  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  7 pending,  7 downloaded,  0 extracted, (25 MB/? MB)
2020-06-07 09:49:58.883  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  6 pending,  8 downloaded,  0 extracted, (26 MB/? MB)
2020-06-07 09:50:00.392  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  5 pending,  9 downloaded,  0 extracted, (28 MB/? MB)
2020-06-07 09:50:01.935  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  4 pending, 10 downloaded,  0 extracted, (30 MB/? MB)
2020-06-07 09:50:03.377  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  3 pending, 11 downloaded,  0 extracted, (32 MB/? MB)
2020-06-07 09:50:04.884  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  2 pending, 12 downloaded,  0 extracted, (34 MB/? MB)
2020-06-07 09:50:23.813  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  1 pending, 13 downloaded,  0 extracted, (58 MB/? MB)
2020-06-07 09:50:24.979  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  1 pending, 13 downloaded,  1 extracted, (59 MB/? MB)
2020-06-07 09:50:25.172  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  1 pending, 13 downloaded,  2 extracted, (60 MB/? MB)
2020-06-07 09:50:25.263  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  1 pending, 13 downloaded,  3 extracted, (60 MB/? MB)
2020-06-07 09:50:25.397  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  1 pending, 13 downloaded,  4 extracted, (60 MB/? MB)
2020-06-07 09:50:25.819  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  1 pending, 13 downloaded,  5 extracted, (61 MB/? MB)
2020-06-07 09:50:25.899  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  1 pending, 13 downloaded,  6 extracted, (61 MB/? MB)
2020-06-07 09:50:25.957  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  1 pending, 13 downloaded,  7 extracted, (61 MB/? MB)
2020-06-07 09:50:26.020  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  1 pending, 13 downloaded,  8 extracted, (61 MB/? MB)
2020-06-07 09:51:02.496  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  0 pending, 14 downloaded,  8 extracted, (108 MB/108 MB)
2020-06-07 09:51:05.070  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  0 pending, 14 downloaded,  9 extracted, (108 MB/108 MB)
2020-06-07 09:51:05.160  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  0 pending, 14 downloaded, 10 extracted, (108 MB/108 MB)
2020-06-07 09:51:05.241  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  0 pending, 14 downloaded, 11 extracted, (108 MB/108 MB)
2020-06-07 09:51:05.303  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  0 pending, 14 downloaded, 12 extracted, (108 MB/108 MB)
2020-06-07 09:51:05.363  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  0 pending, 14 downloaded, 13 extracted, (108 MB/108 MB)
2020-06-07 09:51:05.446  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pulling image layers:  0 pending, 14 downloaded, 14 extracted, (108 MB/108 MB)
2020-06-07 09:51:05.464  INFO 4244 --- [tream-812574485] 🐳 [postgres:12]                         : Pull complete. 14 layers, pulled in 89s (downloaded 108 MB at 1 MB/s)
2020-06-07 09:51:05.475  INFO 4244 --- [    Test worker] 🐳 [postgres:12]                         : Creating container for image: postgres:12
2020-06-07 09:51:05.629  INFO 4244 --- [    Test worker] 🐳 [postgres:12]                         : Starting container with ID: 7f7754ae9a7903583c6a0b1f3cde15a651f0c5fb4394491e1f73a83d9b812c6c
2020-06-07 09:51:06.209  INFO 4244 --- [    Test worker] 🐳 [postgres:12]                         : Container postgres:12 is starting: 7f7754ae9a7903583c6a0b1f3cde15a651f0c5fb4394491e1f73a83d9b812c6c
2020-06-07 09:51:07.193  INFO 4244 --- [    Test worker] 🐳 [postgres:12]                         : Container postgres:12 started in PT1M42.629448S
2020-06-07 09:51:07.202  INFO 4244 --- [    Test worker] i.g.p.b.t.PersonRepositoryTest           : Starting PersonRepositoryTest on archlinux with PID 4244 (started by picodotdev in ../blog-ejemplos/Testcontainers)
2020-06-07 09:51:07.204  INFO 4244 --- [    Test worker] i.g.p.b.t.PersonRepositoryTest           : No active profile set, falling back to default profiles: default
2020-06-07 09:51:07.564  INFO 4244 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFERRED mode.
2020-06-07 09:51:07.612  INFO 4244 --- [    Test worker] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 42ms. Found 1 JPA repository interfaces.
2020-06-07 09:51:07.950  INFO 4244 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-06-07 09:51:08.021  INFO 4244 --- [    Test worker] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-06-07 09:51:08.711  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : SELECT COUNT(*) FROM public.databasechangeloglock
2020-06-07 09:51:08.726  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : CREATE TABLE public.databasechangeloglock (ID INTEGER NOT NULL, LOCKED BOOLEAN NOT NULL, LOCKGRANTED TIMESTAMP WITHOUT TIME ZONE, LOCKEDBY VARCHAR(255), CONSTRAINT DATABASECHANGELOGLOCK_PKEY PRIMARY KEY (ID))
2020-06-07 09:51:08.729  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : SELECT COUNT(*) FROM public.databasechangeloglock
2020-06-07 09:51:08.732  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : DELETE FROM public.databasechangeloglock
2020-06-07 09:51:08.733  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : INSERT INTO public.databasechangeloglock (ID, LOCKED) VALUES (1, FALSE)
2020-06-07 09:51:08.735  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : SELECT LOCKED FROM public.databasechangeloglock WHERE ID=1
2020-06-07 09:51:08.741  INFO 4244 --- [    Test worker] l.lockservice.StandardLockService        : Successfully acquired change log lock
2020-06-07 09:51:09.808  INFO 4244 --- [    Test worker] l.c.StandardChangeLogHistoryService      : Creating database history table with name: public.databasechangelog
2020-06-07 09:51:09.809  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : CREATE TABLE public.databasechangelog (ID VARCHAR(255) NOT NULL, AUTHOR VARCHAR(255) NOT NULL, FILENAME VARCHAR(255) NOT NULL, DATEEXECUTED TIMESTAMP WITHOUT TIME ZONE NOT NULL, ORDEREXECUTED INTEGER NOT NULL, EXECTYPE VARCHAR(10) NOT NULL, MD5SUM VARCHAR(35), DESCRIPTION VARCHAR(255), COMMENTS VARCHAR(255), TAG VARCHAR(255), LIQUIBASE VARCHAR(20), CONTEXTS VARCHAR(255), LABELS VARCHAR(255), DEPLOYMENT_ID VARCHAR(10))
2020-06-07 09:51:09.812  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : SELECT COUNT(*) FROM public.databasechangelog
2020-06-07 09:51:09.813  INFO 4244 --- [    Test worker] l.c.StandardChangeLogHistoryService      : Reading from public.databasechangelog
2020-06-07 09:51:09.813  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : SELECT * FROM public.databasechangelog ORDER BY DATEEXECUTED ASC, ORDEREXECUTED ASC
2020-06-07 09:51:09.815  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : SELECT COUNT(*) FROM public.databasechangeloglock
2020-06-07 09:51:09.823  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : CREATE TABLE public.department (id INTEGER NOT NULL, name VARCHAR(50) NOT NULL, active BOOLEAN DEFAULT TRUE, CONSTRAINT DEPARTMENT_PKEY PRIMARY KEY (id))
2020-06-07 09:51:09.825  INFO 4244 --- [    Test worker] liquibase.changelog.ChangeSet            : Table department created
2020-06-07 09:51:09.826  INFO 4244 --- [    Test worker] liquibase.changelog.ChangeSet            : ChangeSet classpath:/db/changelog/db.changelog-master.xml::1::bob ran successfully in 4ms
2020-06-07 09:51:09.826  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : SELECT MAX(ORDEREXECUTED) FROM public.databasechangelog
2020-06-07 09:51:09.828  INFO 4244 --- [    Test worker] liquibase.executor.jvm.JdbcExecutor      : INSERT INTO public.databasechangelog (ID, AUTHOR, FILENAME, DATEEXECUTED, ORDEREXECUTED, MD5SUM, DESCRIPTION, COMMENTS, EXECTYPE, CONTEXTS, LABELS, LIQUIBASE, DEPLOYMENT_ID) VALUES ('1', 'bob', 'classpath:/db/changelog/db.changelog-master.xml', NOW(), 1, '8:47afc11dcd196aca25eebfad16683784', 'createTable tableName=department', '', 'EXECUTED', NULL, NULL, '3.8.9', '1516269815')
2020-06-07 09:51:09.831  INFO 4244 --- [    Test worker] l.lockservice.StandardLockService        : Successfully released change log lock
...
Hibernate: create sequence hibernate_sequence start 1 increment 1
Hibernate: create table person (id int8 not null, name varchar(255), primary key (id))
...
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: insert into person (name, id) values (?, ?)
Hibernate: select count(*) as col_0_0_ from person person0_
Hibernate: delete from person
Hibernate: select count(*) as col_0_0_ from person person0_
Hibernate: delete from person
System.out

Si fuera necesario Liquibase permite aplicar cambios en la base con archivos de migración, por ejemplo, para crear algunas tablas, procedimientos almacenados o crear los datos básicos. En el ejemplo el archivo de cambios en base de datos incluye la tabla Department. En las trazas se ve la SQL de creación de la tabla, para las pruebas del ejemplo no es necesario pero suele ser una funcionalidad necesaria en un proyecto real.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>  
<databaseChangeLog
  xmlns="http://www.liquibase.org/xml/ns/dbchangelog"  
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd">  

    <changeSet id="1" author="picodotdev">
        <createTable tableName="department">
            <column name="id" type="int">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="name" type="varchar(50)">
                <constraints nullable="false"/>
            </column>
            <column name="active" type="boolean" defaultValueBoolean="true"/>
        </createTable>
    </changeSet>
</databaseChangeLog>
db.changelog-master.xml

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradlew test

Comparte el artículo: