Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TestContainers로 통합 테스트 환경 구축 #1

Merged
merged 2 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ dependencies {
// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'


// postgresql
runtimeOnly 'org.postgresql:postgresql'
Expand All @@ -72,6 +76,11 @@ dependencies {
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'
testImplementation 'com.epages:restdocs-api-spec-mockmvc:0.19.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4'

// testcontainers
testImplementation "org.testcontainers:testcontainers"
testImplementation "org.testcontainers:postgresql"
testImplementation "org.testcontainers:junit-jupiter"
}

openapi3 {
Expand All @@ -92,7 +101,7 @@ swaggerSources {
}
}

test{
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
}
Expand All @@ -102,7 +111,6 @@ jacocoTestReport {
}



generateSwaggerUI {
dependsOn 'openapi3'
delete file('src/main/resources/static/docs/')
Expand All @@ -122,4 +130,4 @@ tasks.register('copySwaggerUI', Copy) {

bootJar {
dependsOn 'copySwaggerUI'
}
}
2 changes: 1 addition & 1 deletion docker-compose-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ services:
- POSTGRESQL_DATABASE=ourhour

# 읽기 전용 데이터베이스
postgesql-standby:
postgresql-standby:
image: 'bitnami/postgresql:latest'
ports:
- '5433:5432'
Expand Down
4 changes: 2 additions & 2 deletions src/main/resources/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ spring:
main:
hikari:
driver-class-name: org.postgresql.Driver
jdbc-url: jdbc:postgresql://localhost:5433/ourhour
jdbc-url: jdbc:postgresql://localhost:5432/ourhour
username: my_user
password: my_password
standby:
hikari:
driver-class-name: org.postgresql.Driver
jdbc-url: jdbc:postgresql://localhost:5433/ourhour
username: my_user
password: my_password
password: my_password
19 changes: 3 additions & 16 deletions src/test/java/com/ourhours/server/DataSourceConfigurationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,23 @@

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.core.env.Environment;
import org.springframework.test.context.ActiveProfiles;

import com.zaxxer.hikari.HikariDataSource;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@SpringBootTest
@ActiveProfiles("dev")
class DataSourceConfigurationTest {

@Autowired
private Environment environment;
class DataSourceConfigurationTest extends IntegrationTestSupporter {

@DisplayName("MainDataSource 설정 테스트")
@Test
void masterDataSourceTest(
void mainDataSourceTest(
@Qualifier(MAIN_DATA_SOURCE) final DataSource mainDataSource) {

// Given
String driverClassName = environment.getProperty("spring.datasource.main.hikari.driver-class-name");
String jdbcUrl = environment.getProperty("spring.datasource.main.hikari.jdbc-url");
Boolean readOnly = Boolean.valueOf(environment.getProperty("spring.datasource.main.hikari.read-only"));
String username = environment.getProperty("spring.datasource.main.hikari.username");

// When
Expand All @@ -42,20 +32,18 @@ void masterDataSourceTest(
log.info("hikari DataSource : [{}]", hikariDataSource);
assertEquals(hikariDataSource.getDriverClassName(), driverClassName);
assertEquals(hikariDataSource.getJdbcUrl(), jdbcUrl);
assertEquals(hikariDataSource.isReadOnly(), readOnly);
assertEquals(hikariDataSource.getUsername(), username);
}
}

@DisplayName("standbyDataSource 설정 테스트")
@Test
void slaveDataSourceTest(
void standbyDataSourceTest(
@Qualifier(STANDBY_DATA_SOURCE) final DataSource standbyDataSource) {

// Given
String driverClassName = environment.getProperty("spring.datasource.standby.hikari.driver-class-name");
String jdbcUrl = environment.getProperty("spring.datasource.standby.hikari.jdbc-url");
Boolean readOnly = Boolean.valueOf(environment.getProperty("spring.datasource.standby.hikari.read-only"));
String username = environment.getProperty("spring.datasource.standby.hikari.username");

// When
Expand All @@ -64,7 +52,6 @@ void slaveDataSourceTest(
log.info("hikari DataSource : [{}]", hikariDataSource);
assertEquals(hikariDataSource.getDriverClassName(), driverClassName);
assertEquals(hikariDataSource.getJdbcUrl(), jdbcUrl);
assertEquals(hikariDataSource.isReadOnly(), readOnly);
assertEquals(hikariDataSource.getUsername(), username);
}
}
Expand Down
87 changes: 87 additions & 0 deletions src/test/java/com/ourhours/server/IntegrationTestSupporter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.ourhours.server;

import static com.ourhours.server.domain.ModuleInformation.*;
import static org.testcontainers.containers.PostgreSQLContainer.*;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
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.core.env.Environment;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.testcontainers.containers.DockerComposeContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.junit.jupiter.Container;

@SpringBootTest
@ActiveProfiles("test")
@ContextConfiguration(initializers = IntegrationTestSupporter.ContainerPropertyInitializer.class)
abstract class IntegrationTestSupporter {

@Autowired
protected Environment environment;

@Container
static final DockerComposeContainer<?> postgresContainer;

static String POSTGRES_MAIN_SERVICE_NAME = POSTGRES_MAIN_SERVICE.getValue();
static String POSTGRES_STANDBY_SERVICE_NAME = POSTGRES_STANDBY_SERVICE.getValue();

static String POSTGRES_MAIN_LOG_REGEX = POSTGRES_MAIN_LOG.getValue();
static String POSTGRES_STANDBY_LOG_REGEX = POSTGRES_STANDBY_LOG.getValue();

static Integer POSTGRES_MAIN_PORT;
static Integer POSTGRES_STAND_BY_PORT;

static String POSTGRES_MAIN_HOST;
static String POSTGRES_STAND_BY_HOST;

static String POSTGRES_MAIN_PREFIX;
static String POSTGRES_STAND_BY_PREFIX;

static String POSTGRES_SUFFIX = POSTGRES_JDBC_URL_SUFFIX.getValue();

static {

postgresContainer = new DockerComposeContainer<>(
new File("src/test/resources/docker-compose-test.yml"))
.withExposedService(POSTGRES_MAIN_SERVICE_NAME, POSTGRESQL_PORT,
Wait.forLogMessage(POSTGRES_MAIN_LOG_REGEX, 1))

.withExposedService(POSTGRES_STANDBY_SERVICE_NAME, POSTGRESQL_PORT,
Wait.forLogMessage(POSTGRES_STANDBY_LOG_REGEX, 1));

postgresContainer.start();

POSTGRES_MAIN_PORT = postgresContainer.getServicePort(POSTGRES_MAIN_SERVICE_NAME, POSTGRESQL_PORT);
POSTGRES_STAND_BY_PORT = postgresContainer.getServicePort(POSTGRES_STANDBY_SERVICE_NAME, POSTGRESQL_PORT);

POSTGRES_MAIN_HOST = postgresContainer.getServiceHost(POSTGRES_MAIN_SERVICE_NAME, POSTGRES_MAIN_PORT);
POSTGRES_STAND_BY_HOST = postgresContainer.getServiceHost(POSTGRES_STANDBY_SERVICE_NAME,
POSTGRES_STAND_BY_PORT);

POSTGRES_MAIN_PREFIX = "jdbc:postgresql://" + POSTGRES_MAIN_HOST + ":";
POSTGRES_STAND_BY_PREFIX = "jdbc:postgresql://" + POSTGRES_STAND_BY_HOST + ":";

}

static class ContainerPropertyInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
Map<String, String> properties = new HashMap<>();
properties.put("spring.datasource.main.hikari.jdbc-url",
POSTGRES_MAIN_PREFIX + POSTGRES_MAIN_PORT + POSTGRES_SUFFIX);
properties.put("spring.datasource.standby.hikari.jdbc-url",
POSTGRES_STAND_BY_PREFIX + POSTGRES_STAND_BY_PORT + POSTGRES_SUFFIX);
TestPropertyValues.of(properties)
.applyTo(context);
}
}
}

Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.ourhours.server;

import static com.ourhours.server.domain.ModuleInformation.*;
import static org.junit.jupiter.api.Assertions.*;

import java.lang.reflect.Method;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.springframework.transaction.annotation.Transactional;

import com.ourhours.server.global.config.database.postgresql.DataSourceType;
Expand All @@ -16,21 +17,18 @@
import lombok.extern.slf4j.Slf4j;

@Slf4j
@SpringBootTest
@ActiveProfiles("dev")
class RoutingDataSourceConfigTest {

private static final String DETERMINE_CURRENT_LOOKUP_KEY = "determineCurrentLookupKey";
@Execution(ExecutionMode.CONCURRENT)
class RoutingDataSourceConfigurationTest extends IntegrationTestSupporter {

@Transactional
@DisplayName("MainDataSource Replication 설정 테스트")
@Test
void testMasterDataSourceReplication() throws Exception {
void testMainDataSourceReplication() throws Exception {
// Given
RoutingDataSource routingDataSource = new RoutingDataSource();

// When
Method declaredMethod = RoutingDataSource.class.getDeclaredMethod(DETERMINE_CURRENT_LOOKUP_KEY);
Method declaredMethod = RoutingDataSource.class.getDeclaredMethod(DETERMINE_CURRENT_LOOKUP_KEY.getValue());
declaredMethod.setAccessible(true);

Object object = declaredMethod.invoke(routingDataSource);
Expand All @@ -49,7 +47,7 @@ void testStandbyDataSourceReplication() throws Exception {
RoutingDataSource routingDataSource = new RoutingDataSource();

// When
Method declaredMethod = RoutingDataSource.class.getDeclaredMethod(DETERMINE_CURRENT_LOOKUP_KEY);
Method declaredMethod = RoutingDataSource.class.getDeclaredMethod(DETERMINE_CURRENT_LOOKUP_KEY.getValue());
declaredMethod.setAccessible(true);

Object object = declaredMethod.invoke(routingDataSource);
Expand Down
19 changes: 19 additions & 0 deletions src/test/java/com/ourhours/server/domain/ModuleInformation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ourhours.server.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ModuleInformation {

POSTGRES_MAIN_SERVICE("test-main"),
POSTGRES_STANDBY_SERVICE("test-standby"),
POSTGRES_MAIN_LOG(".*database system is ready to accept connection.*"),
POSTGRES_STANDBY_LOG(".*database system is ready to accept read-only connections.*"),
POSTGRES_JDBC_URL_SUFFIX("/test_db"),
DETERMINE_CURRENT_LOOKUP_KEY("determineCurrentLookupKey");

private final String value;

}
18 changes: 18 additions & 0 deletions src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
spring:
config:
activate:
on-profile: test

datasource:
main:
hikari:
driver-class-name: org.postgresql.Driver
username: test
password: test
standby:
hikari:
driver-class-name: org.postgresql.Driver
username: test
password: test


31 changes: 31 additions & 0 deletions src/test/resources/docker-compose-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
version: "3.8"
services:
# 쓰기 전용 데이터베이스
test-main:
image: 'bitnami/postgresql:latest'
restart: on-failure
volumes:
- ./replication-user-grant-test.sql:/docker-entrypoint-initdb. d/db.sql
environment:
- POSTGRESQL_REPLICATION_MODE=master # 복제 모드 [master / slave]
- POSTGRESQL_REPLICATION_USER=repl_user # 복제 사용자 이름
- POSTGRESQL_REPLICATION_PASSWORD=repl_password # 복제 사용자 비밀번호
- POSTGRESQL_USERNAME=test
- POSTGRESQL_PASSWORD=test
- POSTGRESQL_DATABASE=test_db

# 읽기 전용 데이터베이스
test-standby:
image: 'bitnami/postgresql:latest'
restart: on-failure
depends_on:
- test-main
environment:
- POSTGRESQL_REPLICATION_MODE=slave # 복제 모드 [master / slave]
- POSTGRESQL_REPLICATION_USER=repl_user # 복제 사용자 이름
- POSTGRESQL_REPLICATION_PASSWORD=repl_password # 복제 사용자 비밀번호
- POSTGRESQL_MASTER_HOST=test-main
- POSTGRESQL_MASTER_PORT_NUMBER=5432
- POSTGRESQL_USERNAME=test
- POSTGRESQL_PASSWORD=test

1 change: 1 addition & 0 deletions src/test/resources/junit-platform.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
junit.jupiter.execution.parallel.enabled=true
2 changes: 2 additions & 0 deletions src/test/resources/replication-user-grant-test.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO repl_user;
ALTER USER REPL_USER WITH SUPERUSER;