Sie können Testcontainer (https://www.testcontainers.org/) verwenden, um einen MySQL-Container nur beim Testen von JUnit zu starten.
Beispielcode SimpleMySQLTest.java Es scheint recht einfach zu bedienen zu sein, aber ich hatte große Probleme, als ich es mit Spring Boot + MyBatis ausprobierte, daher werde ich zusammenfassen, wie es funktioniert.
Schreiben Sie vor dem Testen mit Testcontainern einen Arbeitscode.
Erstellen Sie drei Dateien: UserRepositoryImpl.java, UserMapper.java und UserMapper.xml.
UserRepositoryImpl.java
package springdockerexample.infrastructure.user;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import springdockerexample.domain.user.Name;
import springdockerexample.domain.user.User;
import springdockerexample.domain.user.UserRepository;
import springdockerexample.domain.user.Users;
import java.util.List;
@Repository
public class UserRepositoryImpl implements UserRepository {
@Autowired
private UserMapper mapper;
@Override
public Users findAll() {
List<User> users = mapper.selectAll();
return new Users(users);
}
}
UserMapper.java
package springdockerexample.infrastructure.user;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import springdockerexample.domain.user.Name;
import springdockerexample.domain.user.User;
import java.util.List;
@Mapper
public interface UserMapper {
List<User> selectAll();
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="springdockerexample.infrastructure.user.UserMapper">
<resultMap id="user" type="springdockerexample.domain.user.User">
<result property="name.value" column="name"/>
<result property="age.value" column="age"/>
</resultMap>
<select id="selectAll" resultMap="user">
SELECT name, age FROM users
</select>
</mapper>
Schreiben Sie die DB-Verbindungsinformationen in application.yaml.
application.yaml
spring:
datasource:
url: jdbc:mysql://localhost/mydb
username: user
password: password
driverClassName: com.mysql.cj.jdbc.Driver
Bereiten Sie die Daten für die Entwicklung und das Testen in einer Datei vor.
sql:src/test/resources/docker-entrypoint-initdb.d/init.sql
USE `mydb`;
CREATE TABLE `users` (
`name` VARCHAR(255) NOT NULL,
`age` int NOT NULL
);
INSERT INTO `users` (`name`, `age`) VALUES
('Alice', 20),
('Bob', 30);
Starten Sie MySQL mit Docker Compose, um den Betrieb von Spring Boot + MyBatis zu überprüfen.
Zu diesem Zeitpunkt werden die Daten automatisch eingefügt, indem die zuvor in docker-entrypoint-initdb.d vorbereitete SQL-Datei gemountet wird.
docker-compose.yaml
version: '3'
services:
my-db:
image: mysql:5.7.25
ports:
- 3306:3306
volumes:
- ./src/test/resources/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: mydb
MYSQL_USER: user
MYSQL_PASSWORD: password
Wenn Sie spring-dev-tools einschließen, beginnt es mit ./mvnw spring-boot: run
.
$ ./mvnw spring-boot:run
$ curl localhost:8080/users
{"users":[{"name":"Alice","age":20},{"name":"Bob","age":30}]}
Es funktioniert sicher.
Auf diese Weise ist es möglich, mit einem Container zu testen, der mit Docker Compose gestartet wurde, aber es ist nicht möglich, den Container für jeden Testfall neu zu starten.
Verwenden Sie Testcontainer, um Container während des JUnit-Tests nach Belieben zu starten.
Informationen zum Testen finden Sie unter "Einweg-Datenbankcontainer mit Testcontainern vorbereiten und die Spring Boot-Anwendung testen". Erstellen Sie die folgenden Dateien, um den MySQL-Container zu starten und die Verbindungsinformationen auf den Kontext zu setzen.
MySQLContainerContextInitializer.java
package springdockerexample.testhelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
public class MySQLContainerContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final String MYSQL_IMAGE = "mysql:5.7.25";
private static final String DATABASE_NAME = "mydb";
private static final String USERNAME = "user";
private static final String PASSWORD = "password";
private static final int PORT = 3306;
private static final String INIT_SQL = "docker-entrypoint-initdb.d/init.sql";
private static final String INIT_SQL_IN_CONTAINER = "/docker-entrypoint-initdb.d/init.sql";
private static final Logger LOGGER = LoggerFactory.getLogger(MySQLContainerContextInitializer.class);
private static final MySQLContainer MYSQL = (MySQLContainer) new MySQLContainer(MYSQL_IMAGE)
.withDatabaseName(DATABASE_NAME)
.withUsername(USERNAME)
.withPassword(PASSWORD)
.withExposedPorts(PORT)
.withLogConsumer(new Slf4jLogConsumer(LOGGER))
.withClasspathResourceMapping(INIT_SQL, INIT_SQL_IN_CONTAINER, BindMode.READ_ONLY);
static {
MYSQL.start();
}
@Override
public void initialize(ConfigurableApplicationContext context) {
String mysqlJdbcUrl = MYSQL.getJdbcUrl();
TestPropertyValues.of("spring.datasource.url=" + mysqlJdbcUrl)
.applyTo(context.getEnvironment());
}
}
Ich werde diesen Inhalt der Reihe nach erklären.
private static final MySQLContainer MYSQL = (MySQLContainer) new MySQLContainer(MYSQL_IMAGE)
.withDatabaseName(DATABASE_NAME)
.withUsername(USERNAME)
.withPassword(PASSWORD)
.withExposedPorts(PORT)
.withLogConsumer(new Slf4jLogConsumer(LOGGER))
.withClasspathResourceMapping(INIT_SQL, INIT_SQL_IN_CONTAINER, BindMode.READ_ONLY);
Hier werden fast die gleichen Einstellungen wie in docker-compose.yaml zuvor in Java geschrieben.
Der Bildname wird im neuen MySQLContainer (MYSQL_IMAGE) angegeben. Hier werden auch Einstellungen wie Datenbankname, Benutzername und Kennwort vorgenommen.
Außerdem stellt withClasspathResourceMapping die SQL für die DB-Initialisierung bereit. Es scheint, dass Sie auch die MySQL-Konfigurationsdatei mounten können.
static {
MYSQL.start();
}
Ich starte einen MySQL-Container. MySQL.start () muss vor MySQL.getJdbUrl () in der unten beschriebenen Initialisierung ausgeführt werden.
Ich überspringe dieses Beispiel, aber ich denke, es ist besser, am Ende MySQL.stop () aufzurufen.
Da der von Testcontainern gestartete ** Port für die Verbindung zu MySQL dynamisch zugewiesen wird **, müssen die darauf basierenden Verbindungsinformationen dynamisch festgelegt werden.
Ich habe eine Klasse namens MySQLContainerContextInitializer erstellt, um die Spring Boot-Einstellungen dynamisch zu ändern.
Die spezifische Einstellmethode ist wie folgt.
@Override
public void initialize(ConfigurableApplicationContext context) {
String mysqlJdbcUrl = MYSQL.getJdbcUrl();
TestPropertyValues.of("spring.datasource.url=" + mysqlJdbcUrl)
.applyTo(context.getEnvironment());
}
Die Verbindungsinformationen zu MySQL von der MySQLContainer-Instanz müssen übrigens SimpleMySQLTest.java abgerufen werden. Sie können auch aus dem folgenden Teil von /test/java/org/testcontainers/junit/SimpleMySQLTest.java) sehen.
SimpleMySQLTest.java
@NonNull
protected ResultSet performQuery(MySQLContainer containerRule, String sql) throws SQLException {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(containerRule.getDriverClassName());
hikariConfig.setJdbcUrl(containerRule.getJdbcUrl());
hikariConfig.setUsername(containerRule.getUsername());
hikariConfig.setPassword(containerRule.getPassword());
HikariDataSource ds = new HikariDataSource(hikariConfig);
Statement statement = ds.getConnection().createStatement();
statement.execute(sql);
ResultSet resultSet = statement.getResultSet();
resultSet.next();
return resultSet;
}
Hier ist der Spring Boot Test wie gewohnt.
UserRepositoryImplTest.java
package springdockerexample.infrastructure.user;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import springdockerexample.domain.user.UserRepository;
import springdockerexample.domain.user.Users;
import springdockerexample.testhelper.MySQLContainerContextInitializer;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(initializers = { MySQLContainerContextInitializer.class })
public class UserRepositoryImplTest {
@Autowired
UserRepository userRepository;
@Test
public void test() {
Users users = userRepository.findAll();
assertThat(users.count(), is(2));
}
}
Der Punkt besteht darin, die zuvor mit "@ContextConfiguration (initializers = {SQLContainerContextInitializer.class})" erstellte Klasse anzugeben, MySQL zu starten und die Verbindungsinformationen festzulegen.
Danach können Sie einen normalen JUnit-Test schreiben und problemlos auf die Datenbank zugreifen.
Zusammenfassend ist es überhaupt nicht schwierig, aber in Wirklichkeit war es ziemlich schwierig. Dies kann nützlich sein, wenn Sie es eingerichtet haben.
Da Testcontainer jeden anderen Container als DB unterstützen, können Sie automatisch automatische Tests durchführen.
Die endgültige Dateistruktur des Inhalts dieses Artikels lautet wie folgt.
$ tree
.
├── pom.xml
├── ...
└── src
├── main
│ ├── java
│ │ └── springdockerexample
│ │ ├── SpringDockerExampleApplication.java
│ │ ├── ...
│ │ └── infrastructure
│ │ └── user
│ │ ├── UserMapper.java
│ │ └── UserRepositoryImpl.java
│ └── resources
│ ├── application.yaml
│ └── springdockerexample
│ └── infrastructure
│ └── user
│ └── UserMapper.xml
└── test
├── java
│ └── springdockerexample
│ ├── SpringDockerExampleApplicationTests.java
│ ├── infrastructure
│ │ └── user
│ │ └── UserRepositoryImplTest.java
│ └── testhelper
│ └── MySQLContainerContextInitializer.java
└── resources
└── docker-entrypoint-initdb.d
└── init.sql
Der Quellcode lautet hier.