테스트 코드의 종류와 작성 방법
Unit · Integration · E2E · Acceptance Test · Spring Boot / JUnit

0. 들어가기 앞서

항상 테스트 코드 관련해서 테스트 코드의 필요성을 느끼지 못하고 무조건 작성하려고 했던 것 같다. 또한 테스트의 여러 종류를 학습하고 해당 내용을 바탕으로 필요에 맞게 사용하고 싶어서 공부하게 되었다.

1. 테스트 코드의 필요성

💡
가장 큰 이유는 소프트웨어가 의도한 대로 정확하게 동작하는지 확인하기 위함이다.

개발자는 Swagger, Postman 을 통해 직접 API를 호출하거나, QA 과정을 통해 완성된 소프트웨어를 테스트할 수도 있다.

하지만 프로젝트의 규모가 크고 새로운 기능 추가 시에는 비용이 많이 들게 된다.
  • 테스트 코드를 사용하면, 새로운 기능 추가나 리팩토링 과정에서 기존 요구사항에 영향을 주는 부분을 빠르게 발견할 수 있다.
  • 단기적으로 개발 속도가 느릴 수 있어도, 장기적으로는 굉장히 많은 비용을 아낄 수 있다.

2. 테스트 코드 작성 시 중요하게 생각해야 할 부분

1) 구현이 아닌 '행위 (Behavior)' 를 검증

  • 테스트는 내부 코드 구조(How)가 아니라 외부로 드러나는 결과(What)를 검증해야 한다.
  • 구현 방식을 검증하면, 코드 리팩토링 시 기능은 정상 동작해도 테스트가 깨지는 현상 (Fragile Test)이 발생한다.

2) 명확한 구조화 : Given - When - Then

📋
  • Given (준비) : 테스트에 필요한 상태/데이터 정의
  • When (실행) : 검증할 동작/함수 호출
  • Then (검증) : 예상 결과와 실제 결과 비교

3) 경계값과 예외 상황 (Edge & Negative Cases) 검증

  • 성공하는 케이스보다 "실패하거나 예외가 발생하는 상황"을 잘 검증해야 테스트의 가치가 높다.
  • null, 빈값, 최대/최소 범위, 권한 없음 등 예외 상황 시나리오를 반드시 포함해야 한다.

4) 테스트 간의 완전한 격리 (Independence)

  • 각 테스트는 독립적으로 실행 가능해야 하며, 실행 순서에 영향을 받지 않아야 한다. (메서드 단위)
  • 공통 데이터베이스나 전역 변수를 공유해 다른 테스트 결과에 영향을 주는 부작용을 방지해야 한다.

5) F.I.R.S.T 원칙 체크

Fast · 빠름
피드백을 즉시 받을 수 있도록 실행 속도가 빨라야 함.
Isolated · 격리됨
다른 테스트와 독립적이어야 함.
Repeatable · 반복 가능
로컬, CI environment 등 어떤 환경에서도 동일한 결과를 내야 함.
Self-Validating · 자체 검증
성공/실패 여부를 사람이 확인하지 않고 테스트 framework가 스스로 판단함.
Timely · 적시성
실제 production 코드를 작성하기 전이나 작성 직후 바로 테스트를 작성함.

4. 테스트의 종류

💡
  1. 단위 테스트 (Unit Test)
  2. 통합 테스트 (Integration Test)
  3. E2E 테스트 (End-to-End Test)
  4. 인수 테스트 (Acceptance Test)

⇒ 반드시 모든 종류의 테스트가 존재해야 하는 것은 아니다.
종류 범위 속도 주요 도구
단위 테스트 메서드 / 클래스 ⚡ 빠름 JUnit, Mockito
통합 테스트 서비스 ↔ DB 레이어 🐢 보통 @SpringBootTest
E2E 테스트 전체 시스템 Flow 🐌 느림 TestRestTemplate
인수 테스트 비즈니스 요구사항 — BDD 시나리오

5. 단위 테스트 (with JUnit)

💡
  • 개념 : 시스템에서 가장 작은 단위 (메서드, 클래스) 가 의도대로 동작하는지 검증
  • 주요 특징 : 외부 의존성 (DB, API, 네트워크 등) 을 가짜 객체 (Mock, Stub)로 대체하여 독립적으로 테스트함
  • 실행 속도가 빠르며, 실시간 피드백 가능
단위 테스트의 경우는 현재 개인으로 진행 중인 프로젝트의 구현한 테스트 코드 일부를 가져옴 (완벽하지는 않을 수도 있다.)
User 조회 테스트
java
@Test
@DisplayName("예외: 존재하지 않거나 삭제된 회원이면 IllegalArgumentException 이 발생한다.")
void 유저_조회_예외처리() {

    // given
    Long userId = 999L;
    given(userRepository.findByIdAndDeletedAtIsNull(userId)).willReturn(Optional.empty());

    // when & then
    assertThatThrownBy(() -> userService.findUser(userId))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessage("사용자를 찾을 수 없습니다. ID : " + userId);
}

실제 없는 userId 가 입력되면 조회 시 Optional.empty() 인 빈 값이 나오도록 유도하였고, 해당 서비스의 findUser 메서드를 사용하여 제대로된 예외처리가 이루어지는지 확인하는 코드이다.

given 절에서 일반적인 when 을 사용하는 경우와 달리 given 이라는 키워드를 사용하였는데 이는 BDDMockito 를 사용한 예시이다.

참고 사항 — Mock 객체를 무조건 사용해야 하는 것은 아니다

User 정보 수정 테스트 (실제 객체 사용)
java
@Test
@DisplayName("성공 : 회원 정보 수정 정상 수행")
void 유저_정보_수정() {

    // given
    Long userId = 1L;
    User realUser = User.builder()
        .username("박태우")
        .phoneNumber("010-1234-5678")
        .email("wootaepark@naver.com")
        .build();

    given(userRepository.findByIdAndDeletedAtIsNull(userId)).willReturn(Optional.of(realUser));

    ModifyUserReqDto reqDto = ModifyUserReqDto.builder()
        .phoneNumber("010-1111-2222")
        .build();

    // when
    UpdateUserResDto response = userService.modifyUser(userId, reqDto);

    // then
    assertThat(realUser.getPhoneNumber()).isEqualTo("010-1111-2222");
    assertThat(response).isNotNull();
}

유저 정보를 수정하고 해당 객체의 수정이 제대로 이루어졌는지 확인하기 위해서는 mock 객체보다는 실제 객체 사용이 필요하다.

이외 단위 테스트에 대한 자세한 공부는 (ex : JUnit, Mock) 다른 포스트에서 다룰것이다

6. 통합 테스트

💡
  • 개념 : 단위 모듈들이 서로 결합하여 함께 동작할 때 발생하는 상호작용 및 데이터 흐름을 검증
  • 주요 특징
    • 실제 DB 연동, 파일 시스템 접근, 외부 서비스와의 연결 부위에서 에러가 없는지 확인함.
    • 단위 테스트보다 구성이 복잡하고 실행 시간이 상대적으로 오래 걸림
  • 예시 : 회원 가입 서비스 실행 시, 실제 DB 테이블에 사용자 레코드가 올바르게 저장되는지 검증
통합 테스트 예시 코드
java
@SpringBootTest     // Spring Context 전체를 로드하여 통합 테스트 환경 구축
@Transactional      // 테스트 완료 후 DB 데이터를 자동으로 Rollback
class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    @Test
    @DisplayName("회원가입 신청 시 DB에 정상 저장되고 DTO가 반환되어야 한다")
    void registerUser_Success() {
        // Given
        UserRegisterRequest request = new UserRegisterRequest("hong", "hong@example.com");

        // When
        UserResponse response = userService.registerUser(request);

        // Then
        assertThat(response.getId()).isNotNull();
        assertThat(response.getName()).isEqualTo("hong");

        // DB 직접 조회 검증
        User savedUser = userRepository.findById(response.getId()).orElse(null);
        assertThat(savedUser).isNotNull();
        assertThat(savedUser.getEmail()).isEqualTo("hong@example.com");
    }
}

실제 데이터베이스와 연결하여 (Service → Repository → DB) 로 이어지는 레이어 간 연동을 검증한다.

실행 시 실제 Spring Boot 어플리케이션이 실행되며 실행 속도가 느린 편이다. (+ 실제 객체 사용)


7. E2E 테스트

💡
  • 개념 : 사용자 관점에서 시스템의 처음부터 끝까지 전체 흐름(Flow)이 올바르게 작동하는지 검증
  • 주요 특징
    • UI, 백엔드 API, DB 등 실제 운영 환경과 유사한 조건에서 진행
    • 시스템의 전체적인 신뢰도는 높여주지만, 실행 속도가 가장 느리고 테스트 유지보수 비용이 높다.
  • 예시 : 로그인 → 아이디/비번 입력 → 대시보드 이동 → 장바구니 → 결제 까지의 전체 흐름을 브라우저 자동화 도구로 검증
E2E 테스트 코드 예시
java
// RANDOM_PORT: 실제로 톰캣(Embedded Tomcat) 서버를 포트에 띄움
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderE2ETest {

    // 실제 HTTP 요청을 보낼 클라이언트 객체
    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private OrderRepository orderRepository;

    @Test
    @DisplayName("[E2E] 실제 HTTP POST 요청을 보내 주문을 생성하고 DB 저장까지 확인한다")
    void createOrder_E2E_Success() {
        // Given
        OrderRequest request = new OrderRequest("ITEM-001", 2, 30000);

        // When: 실제 서버 엔드포인트 `/api/orders` 로 HTTP POST 요청 전송
        ResponseEntity<OrderResponse> response = restTemplate.postForEntity(
            "/api/orders",
            request,
            OrderResponse.class
        );

        // Then 1: HTTP 응답 상태 및 Body 검증
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED);
        assertThat(response.getBody()).isNotNull();
        assertThat(response.getBody().getItemCode()).isEqualTo("ITEM-001");
        assertThat(response.getBody().getTotalPrice()).isEqualTo(60000);

        Long createdOrderId = response.getBody().getOrderId();

        // Then 2: 실제 DB에 데이터가 올바르게 영속화되었는지 조회하여 확인
        Order savedOrder = orderRepository.findById(createdOrderId).orElse(null);
        assertThat(savedOrder).isNotNull();
        assertThat(savedOrder.getQuantity()).isEqualTo(2);
    }
}

통합 테스트와 같이 실제 객체들로 테스트하며, 사용자 입장에서 하나의 서비스 flow 전체를 테스트하는 것과 같다.

여러 의존성 및 라이브러리를 가져와서 사용하기 때문에 제일 무거운 테스트


8. 인수 테스트

📋
  • 개념 : 시스템이 비즈니스 요구사항을 충족했는지 사용자 또는 고객(PO/PM) 관점에서 검증
  • 주요 특징
    • "기술적으로 잘 작동하는가?" 보다 "사용자가 요청한 기능 명세대로 동작하는가?" 에 초점
    • 시나리오 기반(BDD)으로 작성하는 경우가 많으며, 해당 테스트 통과 시 배포/수락 가능한 상태로 판단
  • 예시 : "일반 회원이 쿠폰을 적용하면 최종 결제 금액에서 10% 가 할인되어야 한다" 라는 기획 요구사항을 검증

코드로 작성되기보다는 비즈니스 테스트 관점이 더 크다.


9. 마치며

테스트 코드는 프로젝트 환경과 여건에 맞춰 유연하게 적용해야 함을 배웠다.

기존에는 무조건 단위 테스트 위주로 테스트를 진행해야겠다고 생각했지만, 공부해보면서 무조건 테스트 코드를 작성하기보다는 꼭 필요한 곳에 대한 테스트를 적재적소에 사용해야겠다는 생각이 들었다.

앞으로도 더 많은 프로젝트를 진행하며 이번 이론을 바탕으로 실제 테스트 코드를 더 많이 경험하고 적용하고 싶다.

다음 포스트에는 단위 테스트 중에서 제일 많이 사용되는 JUnit 사용 방법에 대해서 자세히 알아 볼 것이다.

@Value vs @ConfigurationProperties

0. 들어가기 앞서

Spring 에서의 환경 변수 관리 방법이 @Value 를 사용하는 방법만 알고 있었는데, 공부하다 보니 좀 더 체계적으로 관리할 수 있는 방법이 있다는 것을 알게 되어 비교하고 각각의 장단점을 정리해보고자 한다.

0-1. 기본 환경 변수 설정

application.properties
properties
greeting-name=taewoo
greeting-coffee=${greeting-name} is drinking Cafe Cereza
application.yml
yaml
greeting:
  name: taewoo
  coffee: ${greeting.name} is drinking Cafe Cereza

1. @Value 를 사용하는 경우

아래와 같이 POJO 형태의 클래스를 먼저 만들었다.

  • POJO : 특정 기술이나 프레임워크에 종속되지 않은, 순수한 형태의 기본 자바 객체

Coffee.java
java
@Component // 빈 등록 필수
@Getter
public class Coffee {

    @Value("${greeting-name:someone}")
    // @Value("${greeting.name:someone}") → yml 파일의 경우
    // application.properties 의 greeting-name 변수 확인 → 없으면 someone 으로 설정
    private final String name;

    @Value("${greeting-coffee:${greeting-name} is drinking ice coffee}")
    // greeting-coffee 변수 확인 → 없으면 : 뒤의 값으로 설정
    private final String place;

    public Coffee() { // 우선순위 확인을 위한 기본 생성자
        this("기본생성자", "여기 있어요");
    }

    public Coffee(String name, String place) {
        this.name = name;
        this.place = place;
    }
}
테스트 코드
java
@SpringBootTest
class CoffeeServiceTest {

    @Autowired
    private CoffeeService coffeeService;

    @Test
    void getCoffee() {
        System.out.println("커피 주문자 : " + coffeeService.getCoffee().getName());
        System.out.println("카페 장소 : " + coffeeService.getCoffee().getPlace());
    }
}
이렇게 기본 생성자가 정의되어 있어도 호출하는 방식에 따라 결과가 다르게 나온다. (개발자 입장에서 헷갈릴 수 있음)

1-1. Spring 컨테이너에 의해 빈으로 생성되는 경우

CoffeeService.java
java
@Service
@RequiredArgsConstructor
public class CoffeeService {

    private final Coffee coffee;

    public Coffee getCoffee() {
        return coffee;
    }
}
💡
동작 과정
  1. Coffee() <기본생성자> 호출
  2. @Value 가 붙은 필드에 설정파일(properties, yml) 의 값을 주입한다.
  3. 결과적으로 환경변수가 기존 생성자로 초기화된 값을 덮어쓰게 된다.

결과


1-2. 직접 클래스 호출

CoffeeService.java
java
@Service
public class CoffeeService {

    // bean 을 통해 주입되는 것이 아니라 직접 생성 (@Component 가 있으나 마나)
    private Coffee coffee = new Coffee();

    public Coffee getCoffee() {
        return coffee;
    }
}
⚠️
Spring 컨테이너에서 주입받은 (DI) 객체가 아닌 새로 만든 객체이기 때문에 @Value 가 동작하지 않는다.
따라서 일반적인 객체 생성과정과 동일하게 이루어지기 때문에 아래와 같은 결과가 나온다.

결과


1-3. @Value 방식의 단점

❗
  1. 테스트의 어려움
    단위 테스트를 위해 객체 생성 시 Spring 컨텍스트를 반드시 띄워야 한다.
    단순 객체 테스트를 위해 Spring 프레임워크를 의존해야 하여 테스트 속도가 느려진다.
  2. final 키워드와의 충돌
    private final String name; 과 같이 final 로 선언하고 싶어도 @Value 는 Spring 이 객체 생성 후 리플렉션(Reflection API) 을 통해 억지로 집어넣는 방식이다.
    final 은 생성자에서 초기화되어야 하는데 필드 주입은 객체 생성 이후에 발생 → final 사용 불가 또는 런타임 오류 발생 가능
  3. 순환 참조 및 초기화 시점 문제
    @Value 주입 시점은 Bean 생성 완료 이후이다.
    생성자 내부 로직에서 @Value 로 주입받은 변수를 사용하려 하면 주입 전이므로 null 값이 나온다.

하지만 이러한 단점 외에도 간결함 및 빠른 프로토타이핑 (복잡 설정 없이 빠르게 기능 구현하거나, 단순 설정 클래스 만들 때) 생산성이 높다.


1-4. 단점 극복 방안 — @Value 를 이용한 생성자 주입

java
@Component
public class Coffee {
    private final String name;
    private final String place;

    // 생성자 주입
    public Coffee(
        @Value("${greeting-name:someone}") String name,
        @Value("${greeting-coffee:${greeting-name} is drinking ice coffee}") String place
    ) {
        this.name = name;
        this.place = place;
    }
}
✅
효과
  1. 테스트 용이 : new Coffee("이름", "장소"); 로 간단히 단위 테스트 가능
  2. 불변성 보장 : final 필드를 안전하게 사용 가능
  3. 순환 참조 방지 : 객체 생성 시점에 모든 의존성이 주입되므로 초기화 오류 없음
  4. DI 프레임워크 의존성 분리 : Spring 이 아닌 다른 환경에서도 독립적으로 동작 가능

1-5. 생성자 주입의 결과

Value 미동작 일반 객체처럼 사용

java
@Service
public class CoffeeService {

    // 기본생성자 호출 시 컴파일 에러
    private final Coffee coffee = new Coffee("이름", "장소");

    public Coffee getCoffee() { return coffee; }
}

Value 동작 의존성 주입 사용

java
@Service
@RequiredArgsConstructor
public class CoffeeService {

    private final Coffee coffee;

    public Coffee getCoffee() { return coffee; }
}

2. @ConfigurationProperties

사용 의도 : 속성을 정의하고 관련 속성을 그룹화해서, 도구로 검증 가능하고 타입 세이프한 방식으로 속성을 참조하고 사용하기 위함

2-1. 사용해보기

Greeting.java
java
@Getter
@Setter
@ConfigurationProperties(prefix = "greeting")
// greeting.name, greeting.coffee 처럼 application.properties 에 설정 가능
public class Greeting {
    private String name;
    private String coffee;
}
TaepangApplication.java (main)
java
@SpringBootApplication
@ConfigurationPropertiesScan
// @ConfigurationProperties 를 스캔하기 위해 필요 (또는 Greeting 에 @Component 추가)
public class TaepangApplication {
    public static void main(String[] args) {
        SpringApplication.run(TaepangApplication.class, args);
    }
}
CoffeeService.java
java
@Service
@RequiredArgsConstructor
public class CoffeeService {

    private final Greeting greeting; // 의존성 주입

    public Greeting getCoffee() { return greeting; }
}
build.gradle
groovy
implementation 'org.springframework.boot:spring-boot-configuration-processor'
application.properties
properties
greeting.name=newName
greeting.coffee=helloCoffee
// 위 의존성 추가 이후 재빌드 하면 위와 같이 기본 변수 설정이 가능하다.
테스트 코드
java
@SpringBootTest
class CoffeeServiceTest {

    @Autowired
    private Greeting greeting;

    @Test
    void getCoffee() {
        System.out.println("커피 주문자 : " + greeting.getName());
        System.out.println("카페 장소 : " + greeting.getCoffee());
    }
}

테스트 결과


💡
@Value 와의 차이
  1. 설정(환경 변수)을 하나의 POJO 로 정의할 수 있다.
  2. IDE 의 자동완성 가능
  3. 기본값 관리의 명확성 : 코드 내에서 직접 확인 가능
    java
    @ConfigurationProperties(prefix = "coffee")
    public class CoffeeProperties {
        private String name = "someone";       // 자바 자체의 초기값
        private String place = "drinking ice coffee";
    }
    객체 생성 시점에 이미 초기값이 설정되므로, Spring 이 값을 주입하지 않아도 항상 안전하고 유효한 상태이다.
  4. List, Map, 중첩 객체 등 계층형 구조를 깔끔하게 매핑 가능하다.
  5. @Valid 와 함께 사용하여 시작 시 설정값 검증 가능 (오류 발생 시 서버 중단) — 제일 중요
    java
    @ConfigurationProperties(prefix = "coffee")
    @Validated
    public class CoffeeProperties {
        @NotBlank
        private String name;  // 반드시 값이 있어야 함
    
        @Min(1)
        private int price;    // 가격은 최소 1 이상이어야 함
    }

3. @ConfigurationProperties 의 추가 기능 — 서드 파티 옵션

3-1. 서드 파티란?

📖
서드 파티(Third-party)는 '제3자'를 뜻한다. 원천 기술이나 서비스를 제공하는 당사자(퍼스트 파티)와 이를 이용하는 소비자(세컨드 파티)가 아닌, 외부의 다른 개발자나 기업을 의미 (from 나무위키)

Spring 관점의 서드 파티
  • JDBC / ORM
  • Auth0, Okta
  • Lombok, Swagger
  • Redis, Kafka, AWS SDK

Spring 프로젝트에 외부 의존성을 추가하고 해당 의존성(컴포넌트) 문서를 참조해 스프링 빈을 생성할 때 @ConfigurationProperties 가 유용하게 사용된다.


3-2. 예제 컴포넌트 생성해서 적용해보기

Droid.java
java
@Getter
@Setter
public class Droid { // 외부에서 가져온 서드 파티 컴포넌트라고 가정
    // getter, setter 를 통해 Reflection API 로 환경변수가 설정된다.
    private String id, description;
}
SpringConfig.java — 빈 등록 (수동)
java
@Configuration
public class SpringConfig {

    @Bean
    @ConfigurationProperties(prefix = "droid")
    // 외부 서드파티에 직접 붙일 수 없는 경우 사용 (원래는 클래스 위가 권장)
    Droid droid() {
        return new Droid(); // Setter 를 통해 객체의 설정값 주입
    }
}
application.properties
properties
droid.id=BB-8
droid.description=Small, rolling android, Doesn't drink milk.
테스트 코드
java
@SpringBootTest
class CoffeeServiceTest {

    @Autowired
    private Droid droid;

    @Test
    void getCoffee() {
        System.out.println("드로이드 id : " + droid.getId());
        System.out.println("드로이드 설명 : " + droid.getDescription());
    }
}

결과

이런 식으로 @ConfigurationProperties 를 이용하여 서드 파티 컴포넌트의 환경변수를 설정하고 사용 가능하다.


3-3. Setter 없이 생성자로만 빈 등록해서 사용하기 (서드 파티)

Droid.java — record 클래스로 변경
java
// record 클래스 (@Getter, @RequiredArgsConstructor 포함)
public record Droid(String id, String description) {
    // droid.id(); / droid.description(); 처럼 접근 가능
}

// ─── 아래 코드와 동일함 ───

@Getter
public class Droid {
    private final String id, description;

    public Droid(String id, String description) {
        this.id = id;
        this.description = description;
    }
}

3-3-1. @Value 사용

java
@Configuration
public class SpringConfig {

    @Value("${droid.id:defaultId}")
    private String id;

    @Value("${droid.description:defaultDesc}")
    private String description;

    @Bean
    @ConfigurationProperties(prefix = "droid")
    Droid droid() {
        return new Droid(id, description);
    }
}
💡
환경 변수를 가져와서 생성자를 통해 Bean 등록 (필요 변수)

결과

3-3-2. @ConfigurationProperties 사용

Droid.java 수정
java
@ConfigurationProperties(prefix = "droid")
public record Droid(String id, String description) { }

해당 클래스를 환경변수에 이용한다는 @ConfigurationProperties 설정

SpringConfig.java
java
@Configuration
// 방법 1. Droid 를 빈 등록 (이것만 쓰는 경우 Setter 필수 / @ConfigurationPropertiesScan 없어도 됨)
@EnableConfigurationProperties(Droid.class)
public class SpringConfig {

    // 방법 2. 직접 빈 등록 (Setter 없는 경우 보통 이 방법 사용 / @ConfigurationPropertiesScan 필수)
    @Bean
    Droid droid(Droid dro) {
        return new Droid(dro.id(), dro.description());
    }
}

방법 한 개만 써야 중복 빈 등록이 방지된다. (상황에 맞게 사용)

결과


📌
정리
  • Setter 가 있으면 : @ConfigurationPropertiesScan 없어도 됨. 여러 방법 존재 (위 SpringConfig.java 참고)
  • 생성자를 통해 초기화해야 하면 : @ConfigurationPropertiesScan 필수 (환경 변수 주입이 필요하기 때문)

💡 결론 : 웬만하면 @ConfigurationPropertiesScan 사용하면서 생성자를 통해 초기화하자!!

4. .env 와 @ConfigurationProperties 사용 (TIP)

Greeting.java
java
@Getter
@Setter
@ConfigurationProperties(prefix = "greeting")
public class Greeting {
    private String name;   // GREETING_NAME 매핑
    private String coffee; // GREETING_COFFEE 매핑
}
.env
GREETING_NAME=envName
GREETING_COFFEE=envCoffee
application.properties
properties
# 실제 배포 시 환경 변수로 관리 (해당 파일이 GitHub 에 올라가야 하기 때문)
greeting.name=${GREETING_NAME:someone}
greeting.coffee=${GREETING_COFFEE:americano}

Intellij 설정 (화면 우측 상단의 editConfiguration 설정을 하면 된다.)

결과


5. 후기

🙂
기존까지는 단순히 @Value 를 쓴 경험뿐이었다. 대부분의 프로젝트나 실습 때 환경변수를 단순히 가져다만 썼기 때문이라고 생각한다.

@ConfigurationProperties 를 쓰는 방법도 알았으니 앞으로 더 복잡한 변수 관리가 필요할 때 유용하게 사용할 수 있을 것 같다.

'Spring boot' 카테고리의 다른 글

[Spring] 테스트 코드 알아보기  (1) 2026.07.22

+ Recent posts