본문 바로가기

프로젝트/Techfork

[26/01/20] 오늘의 개발 일지 - CI에서 테스트 실행 및 통합 테스트 컨테이너 환경 개선

개발 일지가 꽤나 밀려버렸습니다.

일단 오늘 일지부터 작성하고 천천히 밀린 걸 써보겠습니다.

 

오늘은 드디어 Github Actions의 CI 과정에 테스트를 도입했습니다.

사실 지금까지는 gradle의 -x test 옵션으로 테스트 없이 빌드만 진행했었습니다.

 

검색과 추천 성능평가 및 데이터 셋업 테스트 때문에 바로 옵션을 빼서 테스트를 진행하기가 애매했기에

조금 시간이 걸렸네요.

 

1. Github Actions CI 워크플로우 개선

      - name: Build with Gradle
        run: ./gradlew build -x test --no-daemon

기존의 다음과 같은 코드에서

 

      - name: Build with all tests
        run: ./gradlew build --no-daemon

위와 같은 코드로 변경하였습니다.

 

초기에는 PR 생성 시에는 통합 테스트를 진행하지 않을까 했지만

통합 테스트를 포함하여도 현재 3분만에 CI 과정이 끝나므로 포함하였습니다.

 

@Tag("evaluation")
@Slf4j
public class LambdaOptimizationTest extends RecommendationTestBase {

 

@Tag("evaluation-setup")
@Disabled("데이터 셋업용 - CI 제외")
@Slf4j
public class RecommendationTestDataSetup extends RecommendationTestBase {

문제가 되었던 평가 테스트와 데이터 셋업은 JUnit의 태그 기능을 활용하여 테스트를 분리했습니다.

 

tasks.named('test') {
	useJUnitPlatform()		useJUnitPlatform {
		if (project.hasProperty('excludeIntegration')) {
			excludeTags 'integration'
		}
		excludeTags 'evaluation-setup', 'evaluation'
	}

	testLogging {
		events "passed", "skipped", "failed"
		exceptionFormat "full"
		showStandardStreams = false
	}
}	}

tasks.register('integrationTest', Test) {
	useJUnitPlatform {
		includeTags 'integration'
		excludeTags 'evaluation-setup', 'evaluation'
	}

	testLogging {
		events "passed", "skipped", "failed"
		exceptionFormat "full"
		showStandardStreams = false
	}

	shouldRunAfter tasks.named('test')
}

tasks.register('evaluationTest', Test) {
	useJUnitPlatform {
		includeTags 'evaluation'
		excludeTags 'evaluation-setup', 'integration'
	}

	testLogging {
		events "passed", "skipped", "failed"
		exceptionFormat "full"
	}
}

위와 같이 build.gradle을 구성하여

 

전체 테스트(통합테스트 제외 옵션 O, 단위 + 통합),

통합 테스트(단위 + 통합)

평가 테스트

를 각각 진행할 수 있도록 task를 구성했습니다.

 

@Tag("integration")
@SpringBootTest
@AutoConfigureMockMvc
@Import(IntegrationTestConfig.class)
@ActiveProfiles("integrationtest")
public abstract class IntegrationTestBase {
}

통합 테스트 클래스들은 다음 추상 클래스를 상속하도록 하여 코드 중복을 제거하였고,

환경 설정또한 통일하였습니다.

 


2. 테스트 컨테이너에 싱글턴 패턴 적용

기존에는 통합 테스트마다 테스트 컨테이너가 생성되었다가 삭제되며,

이로 인한 자원 소모 및 시간 소모가 심했습니다.

 

각각의 통합테스트를 실행하는 경우보다 전체 통합 테스트를 한 번에 실행하는 경우가 많으므로

통합 테스트 환경을 통일하고,

이 컨테이너들을 싱글턴 패턴을 적용해 같은 JVM 내에서 재사용할 수 있도록 적용하였습니다.

 

package com.techfork.global.configuration;

import com.redis.testcontainers.RedisContainer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;
import org.testcontainers.containers.MySQLContainer;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.utility.DockerImageName;

@TestConfiguration(proxyBeanMethods = false)
public class IntegrationTestConfig {

    private static final ElasticsearchContainer elasticsearch =
            new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:8.18.0")
                    .withEnv("xpack.security.enabled", "false")
                    .withEnv("discovery.type", "single-node")
                    .withEnv("ES_JAVA_OPTS", "-Xms256m -Xmx256m");

    private static final MySQLContainer<?> mysql =
            new MySQLContainer<>("mysql:8.0.36");

    private static final RedisContainer redis =
            new RedisContainer(DockerImageName.parse("redis:7.2-alpine"));

    static {
        elasticsearch.start();
        mysql.start();
        redis.start();
    }

    @Bean
    @ServiceConnection
    ElasticsearchContainer elasticsearchContainer() {
        return elasticsearch;
    }

    @Bean
    @ServiceConnection
    MySQLContainer<?> mySQLContainer() {
        return mysql;
    }

    @Bean
    @ServiceConnection
    RedisContainer redisContainer() {
        return redis;
    }
}

이를 통해 실행 시간(175초 -> 60초)과 메모리 사용(3GB -> 1GB) 부분에서

약 60%정도의 감소를 이뤄냈습니다.

 

앞으로 테스트 커버리지를 늘려서 더더욱 안정성있는 어플리케이션을 보장하도록 노력하겠습니다.