들어가며
CCommit 멘토링 중에 Mockito와 JUnit 5 관련해서 이야기가 나왔는데, Mockito가 JUnit 5에 포함되어 있는 개념으로만 알고 있다고 대답했습니다. 테스트 코드를 여러 번 짜봤지만 자세히는 몰랐던 것이죠.
지금까지 멘토링을 진행하면서 배우고 느낀것은 기술의 등장 배경과 장단점을 잘 알고 쓰는 개발자가 좋은 개발자라는 것입니다.
그렇다면 이제는 그냥 넘어갈 수 없죠.
잘못 알거나 모르는 건 괜찮습니다. 잘못 안다는 것은 학습의 계기가 될 수 있고, 모른다는 것은 배울 수 있는 기회이자 엄청난 성장 가능성이기 때문이죠.
저는 아직 모르는 것도 많고 공부할 것도 많지만 엄청난 성장 가능성이 있는 개발자라고 생각합니다.
새벽에 글을 작성하다 보니 감성에 젖어 서론이 조금 길어졌네요.
이번 글에서는 Mockito와 JUnit 5의 차이점과 장단점, 그리고 Mock, Stub, Spy 개념에 대해 작성해보려고 합니다.
0. 내가 오해하고 있었던것
Mockito가 JUnit 5에 포함되어 있는 개념이 아닙니다.
JUnit 5는 테스트 프레임워크이고, Mockito는 모킹(mocking) 라이브러리입니다.
프레임워크(Framework)
프레임워크는 개발자가 작성한 코드를 호출하고 실행 흐름을 관리하는 구조를 제공합니다. 개발자는 프레임워크의 규칙과 구조에 맞춰 코드를 작성하며 프레임워크가 전체 프로그램의 흐름을 제어합니다. 즉, 프레임워크는 ‘나를 호출하는 구조’라고 이해하면 쉽습니다.
라이브러리(Library)
라이브러리는 개발자가 필요할 때 직접 호출해 사용하는 코드 모음입니다. 개발자가 흐름과 시점을 결정하며 원하는 기능만 가져다 쓸 수 있습니다. 즉, 라이브러리는 ‘내가 호출하는 도구’라고 생각하면 이해하기 쉽습니다.
프레임워크와 라이브러리 차이
라이브러리는 개발자가 필요할 때 직접 호출해서 사용하는 도구인 반면, 프레임워크는 개발자가 작성한 코드를 실행 흐름에 맞춰
프레임워크가 호출하고 관리 하는 구조입니다.
1. Mockito란 무엇인가?
Mockito는 자바에서 가장 널리 쓰이는 Mocking 라이브러리입니다.
즉 테스트 대상 객체가 의존하는 객체를 흉내 내어 테스트할 수 있도록 돕는 도구입니다. 이를 통해 외부 시스템에 의존하지 않고도 테스트를 수행할 수 있어 단위 테스트의 속도와 안정성을 높여줍니다.
등장 배경
단위 테스트를 작성할 때 외부 의존성,예를 들어 데이터베이스(DB), 외부 API, 네트워크 통신 등으로 인해 테스트가 느려지거나 실패하는 경우가 많았습니다.
Mockito는 이러한 문제를 해결하고, 빠르고 안정적인 단위 테스트를 가능하게 하기 위해 등장했습니다.
초기 버전은 2008년경에 만들어졌으며, 현재는 거의 표준처럼 사용되고 있습니다.
더 자세한 내용이 궁금하시다면 클릭 .
Mockito의 장점
Mockito를 사용하면 외부 시스템 없이 테스트를 수행할 수 있어 테스트 속도가 빨라지고 반복 실행에도 효율적입니다.
또한, 특정 메소드의 동작을 정의할 수 있어 테스트 결과를 예측 가능하게 만들 수 있으며, 메소드 호출 여부나 호출 횟수를 검증할 수 있어 행위 검증(Behavior Verification)이 가능합니다.
이 덕분에 단위 테스트의 정확성을 높이고, 테스트 코드가 실제 로직을 효과적으로 검증하도록 도와줍니다.
예를 들어 데이터베이스에 의존하는 서비스를 테스트한다고 가정해봅시다.
실제 DB를 연결하면 테스트가 느려지고, DB 상태에 따라 테스트 결과가 달라질 수 있습니다.
이때 Mockito를 사용하면 DB 접근을 Mock 객체로 대체하고, 특정 쿼리 호출 시 원하는 결과를 미리 정의할 수 있습니다.
따라서 테스트는 빠르게 실행되며 DB 상태에 영향을 받지 않고 항상 동일한 결과를 검증할 수 있습니다.
또 다른 예시로 외부 API 호출이 포함된 서비스를 테스트할 때도 Mockito를 사용하면 실제 API 서버를 호출하지 않고 원하는 응답을 Stub으로 정의할 수 있습니다.
이렇게 하면 네트워크 지연이나 API 장애와 무관하게 테스트를 안정적으로 수행할 수 있습니다.
결과적으로 Mockito는 단순히 Mock 객체를 만들어주는 것 이상의 테스트 안정성과 예측 가능성을 높이는 도구로 활용됩니다.
Mockito 사용예시
public class UserServiceTest {
@Test
void simpleMockitoTest() {
// 1. Mock 생성
UserRepository repo = mock(UserRepository.class);
UserService service = new UserService(repo);
// 2. Stub 정의
when(repo.findById(1L)).thenReturn(new User("Alice"));
// 3. 테스트 실행
String name = service.getUserName(1L);
assertEquals("Alice", name);
// 4. 호출 검증
verify(repo).findById(1L);
}
}
위 코드를 보면 먼저 mock() 메소드를 사용하면, 실제 객체 대신 테스트용으로 동작하는 가짜 객체(Mock 객체)를 생성할 수 있습니다. 이렇게 하면 데이터베이스, 외부 API 등 실제 환경에 의존하지 않고도 테스트를 진행할 수 있습니다.
그다음 when(...).thenReturn(...) 구문을 사용하면, Mock 객체의 특정 메소드가 호출될 때 미리 정의한 값을 반환하도록 설정할 수 있습니다. 하지만 테스트의 정확성을 높이기 위해서는 AAA(Arrange - Act - Assert) 순서를 지키는 것이 중요합니다.
AAA(Arrange - Act - Assert) 순서란?
AAA는 단위 테스트를 작성할 때 가장 널리 사용되는테스트 구조 패턴 입니다.
이름 그대로 테스트 코드를 세 단계로 나누어 작성하는 방식입니다.
Arrange (준비) : 테스트를 위한 세팅 단계
테스트에 필요한 객체와 환경을 준비하는 단계입니다.
Mock 객체를 생성하거나, Stub 동작을 정의하고, 테스트에 사용할 데이터를 세팅합니다.
Act (실행) : 검증할 행동을 수행하는 단계
실제로 테스트하려는 메소드를 호출하는 단계입니다. 핵심 로직을 한 번 실행합니다.
Assert (검증) : 결과가 맞는지 확인하는 단계
실행 결과가 기대한 값과 일치하는지 확인하는 단계입니다.
또는 특정 메소드가 호출되었는지 검증합니다.
AAA를 지켜야 테스트 정확도가 높아지는 이유
AAA 구조를 지키는 이유는 단순히 가독성을 높이기 위함이 아니라 테스트의 신뢰성을 보장하기 위해서입니다.
먼저 Arrange 단계에서 Mock 객체와 Stub 동작을 명확히 정의해야 합니다. Mock은 기본적으로 아무 동작도 하지 않기 때문에 when(...).thenReturn(...) 설정이 선행되지 않으면 실제 메소드 호출 시 기본값(null, 0, false 등)을 반환하게 됩니다. 이 상태에서 Act가 먼저 실행되면 테스트는 의도하지 않은 값으로 동작하게 되고, 잘못된 성공이나 실패가 발생할 수 있습니다.
또한 Act 단계는 검증하고자 하는 핵심 동작을 한 번만 실행해야 합니다. 만약 여러 번 실행되거나 Arrange와 섞이게 되면, 어떤 실행 결과를 검증하는 것인지 모호해집니다. 특히 verify()를 사용하는 경우, 실행 횟수와 호출 순서가 중요한데 AAA 구조가 깨지면 이러한 검증이 왜 실패했는지 추적하기 어려워집니다.
마지막으로 Assert 단계는 실행 이후의 결과와 행위를 검증하는 역할을 합니다. 결과 값만 확인하는 것이 아니라, 의도한 협력 객체(Mock)가 실제로 호출되었는지까지 검증해야 테스트가 의미를 가집니다. Arrange와 Assert가 섞이거나, Act 이전에 검증을 수행하면 테스트는 항상 실패하거나 잘못된 판단을 내릴 수 있습니다.
즉, AAA는 단순한 작성 규칙이 아니라
- 테스트 흐름을 명확히 분리하고
- Stub 설정이 실행 전에 보장되며
- 검증 대상 행위가 정확히 한 번 실행되도록 하고
- 결과와 행위 검증의 책임을 명확히 나누는
테스트의 구조적 안정성을 확보하는 패턴입니다.
결과적으로 AAA를 지키면 테스트가 우연히 통과하는 일을 방지할 수 있고, 외부 환경이나 내부 구현 변경에 흔들리지 않는 신뢰도 높은 단위 테스트를 작성할 수 있습니다.
예를 들어 무신사 주문 서비스에서 PG 결제와 배송 기능을 단위 테스트한다고 가정해봅시다.
1.유저가 주문하고 결제가 성공하는 경우
// Arrange: Mock 객체와 Stub 설정
PaymentGateway pgMock = mock(PaymentGateway.class);
when(pgMock.processPayment(any(Order.class))).thenReturn(PaymentResult.SUCCESS);
DeliveryService deliveryMock = mock(DeliveryService.class);
when(deliveryMock.scheduleDelivery(any(Order.class))).thenReturn(DeliveryResult.SUCCESS);
OrderService orderService = new OrderService(pgMock, deliveryMock);
// Act: 실제 주문 실행
Order order = new Order(userId, productId);
OrderResult result = orderService.placeOrder(order);
// Assert: 결과와 호출 검증
assertEquals(OrderStatus.COMPLETED, result.getStatus());
verify(pgMock).processPayment(order);
verify(deliveryMock).scheduleDelivery(order);
위 예시에서 알 수 있듯이, Mock과 Stub을 먼저 준비(Arrange)하고, 실제 코드를 실행(Act)한 뒤, 결과와 호출 검증(Assert)을 수행해야 테스트의 정확도가 높아집니다.
만약 순서를 바꾸거나 설정과 검증이 꼬이면, Mock의 반환값과 실제 호출 검증이 일치하지 않아 테스트가 잘못 통과하거나 실패할 수 있습니다.
이를 통해 테스트에서 원하는 결과를 안정적으로 얻을 수 있으며, 외부 환경에 따라 결과가 달라지는 문제를 방지할 수 있습니다.
마지막으로 verify() 메소드는 Mock 객체의 메소드가 실제로 호출되었는지 검증하는 역할을 합니다. 이를 통해 단순히 결과만 맞는지 확인하는 것이 아니라, 테스트 대상 코드가 의도한 행위를 수행했는지까지 검증할 수 있습니다.
Mockito의 단점
반대로 Mockito를 지나치게 의존하면 테스트가 실제 구현에 종속될 수 있습니다.
잘못된 Mock 설정으로 인해 실제 로직과 테스트 결과가 차이가 날 수 있으며, Stub이나 Spy 같은 개념을 이해하지 못하면 오히려 테스트 작성이 복잡해질 수 있습니다.
따라서 Mockito를 효과적으로 사용하기 위해서는 개념과 사용법을 충분히 이해하고, 실제 테스트 목적에 맞게 적절히 활용하는 것이 중요합니다.
예를 들어 Mock 객체의 동작을 너무 세부적으로 정의하다 보면, 테스트가 실제 코드 구현 방식에 맞춰져 버립니다. 이 경우 코드가 조금만 바뀌어도 테스트가 깨지기 때문에 테스트 유지보수가 어려워집니다.
저 또한 이런경험이 있었는데 이런 이유때문이었던것 같습니다.
또한 Mock 설정이 잘못되면 실제 로직과 테스트 결과가 달라질 수 있습니다.
예를 들어 데이터베이스를 Mock으로 대체하면서 반환값을 잘못 정의하면 테스트에서는 성공하지만 실제 실행 환경에서는 오류가 발생할 수 있습니다.
// 잘못된 Mock 예시
when(userRepository.findById(1L)).thenReturn(new User(2L, "Bob"));
String name = userService.getUserName(1L);
assertEquals("Alice", name); // 실패! 실제 로직과 Mock 결과 불일치
이처럼 Stub이나 Spy 같은 개념을 충분히 이해하지 못하면 Mock 객체를 남용하거나 잘못 사용할 수 있어 테스트가 복잡하고 신뢰성이 떨어지게 됩니다.
Spy를 사용해야 할 상황에서 Mock만 사용하거나 반환값만 Stub으로 정의하고 행위 검증을 하지 않는 등도 문제가 될 수 있습니다.
따라서 Mockito를 효과적으로 사용하려면, Mock, Stub, Spy의 차이와 사용법을 충분히 이해하고, 테스트 목적에 맞게 적절히 활용하는 것이 중요합니다.
Mockito 핵심 개념: Mock, Stub, Spy
위 Mockito의 단점 부분에서 Mock, Stub, Spy의 차이와 사용법이 얼마나 중요한지 아셨죠?
단위 테스트에서 중요한 것은 테스트 대상 외부 의존성을 어떻게 대체하느냐입니다.
Mockito는 이를 위해 Mock, Stub, Spy라는 세 가지 핵심 개념이 있습니다.
1. Mock - 모든 것이 가짜인 객체, 메소드 호출과 횟수까지 검증
Mock은 테스트 대상 객체가 의존하는 객체를 완전히 가짜로 만든 객체입니다.
기본적으로 모든 메소드는 아무 동작도 하지 않으며, 필요한 동작만 정의할 수 있습니다.
Mock을 사용하면 메소드 호출 여부나 호출 횟수까지 검증할 수 있어 행위 검증이 가능합니다.
장점: 외부 시스템 없이 단위 테스트 가능, 테스트 예측 가능, 행위 검증 가능
단점: 실제 로직과 동작이 달라질 수 있고, 지나치게 의존하면 테스트가 구현에 종속될 수 있음
UserRepository repo = mock(UserRepository.class);
when(repo.findById(1L)).thenReturn(new User(1L, "Alice")); // Stub 역할
verify(repo).findById(1L); // 호출 검증
2.Stub - 결과만 미리 정의하는 객체, 테스트 결과 예측에 특화
Stub은 특정 메소드 호출 시 미리 정의된 결과를 반환하도록 설정한 객체입니다.
테스트 결과만 필요할 때 사용하며, 호출 여부 검증은 필요하지 않습니다.
장점: 테스트 결과 예측 가능, 외부 의존성 없이 결과 시뮬레이션 가능
단점: 행위 검증 불가, 동작 확인이 필요하면 추가 코드 필요
when(repo.findById(1L)).thenReturn(new User(1L, "Alice")); // Stub 설정
3.Spy- 실제 객체를 기반으로 일부 동작만 조작, 실제 동작과 Mock의 장점 결합
Spy는 실제 객체를 감싸서 일부 메소드만 Mock처럼 조작할 수 있는 객체입니다.
기본적으로 원본 객체의 메소드가 실행되지만, 특정 메소드만 Stub/Mock 처리할 수 있습니다.
장점: 객체 일부만 Mock 처리하고 나머지는 실제 동작 테스트 가능, 복잡한 객체 테스트에 유용
단점: 원본 객체 동작이 포함되므로 의도치 않은 부작용 가능, 일부 동작이 실제로 실행됨
List<String> list = new ArrayList<>();
List<String> spyList = spy(list);
spyList.add("A");
spyList.add("B");
when(spyList.size()).thenReturn(100); // size()만 Stub
assertEquals(100, spyList.size());
assertEquals("A", spyList.get(0)); // 실제 메소드 호출
Mock, Stub, Spy 비교
구분 Mock Stub Spy
| 실제 객체 사용 | X | X | O |
| 메소드 호출 검증 | 가능 | X | 가능 |
| 특정 반환값 정의 | 가능 | 가능 | 가능 |
| 장점 | 행위 검증, 외부 의존성 제거 | 결과 예측 가능 | 실제 객체 + 일부 Mock 가능 |
| 단점 | 실제 로직과 차이날 수 있음 | 행위 검증 불가 | 원본 객체 부작용 가능 |
이렇게 기억하면 쉬워요
- Mock → 모든 게 가짜, 필요한 동작만 설정
- Stub → 반환 값만 미리 정의
- Spy → 실제 객체 + 일부 조작
JUnit 5에서는 가볍게 다루고 추후에 더 자세히 작성하겠습니다.
2. JUnit 5란 무엇인가?
JUnit 5는 자바에서 테스트를 작성하고 실행하기 위한 테스트 프레임워크입니다.
기존 JUnit 4에 비해 모듈화와 유연성이 향상되어 조건별 테스트, 반복 테스트, 동적 테스트 등 다양한 테스트 시나리오를 지원합니다.
JUnit 5는 테스트 메소드 실행, Assertion 제공, Before/After 같은 라이프사이클 관리 등 테스트 실행과 구조를 관리하는 역할을 합니다.
JUnit 5 구조
JUnit 5는 크게 JUnit Platform, JUnit Jupiter, JUnit Vintage, Third Party으로 나뉩니다.
왜 Vintage, Jupiter, Third Party, Platform으로 분리했을까?
JUnit 5는 단순히 JUnit 4의 업그레이드 버전이 아닙니다. 기존 구조를 유지한 채 기능만 추가한 것이 아니라, 테스트 실행 구조 자체를 재설계한 것입니다.
정리하면 확장성과 호환성을 동시에 확보하기 위함입니다.

JUnit Platform
JUnit Platform은 JUnit 5의 기반이 되는 모듈로, JVM 위에서 테스트를 실행할 수 있는 런칭 메커니즘을 제공합니다.
실제 애플리케이션에서는 테스트 팀이 다양한 테스트 케이스를 설계하여 기능과 동작을 검증합니다. JUnit Platform은 이러한 테스트가 어떤 JVM 환경에서도 일관되게 실행되도록 보장합니다.
JUnit Platform을 분리한 이유
JUnit Platform은 테스트를 실행하는 기반 역할을 합니다. 테스트를 “어떻게 작성하느냐”와 “어떻게 실행하느냐”를 분리함으로써, 테스트 실행 구조를 표준화하고 다양한 테스트 엔진을 유연하게 연결할 수 있도록 만들었습니다.
즉, 실행 환경(IDE, Maven, Gradle)은 그대로 두면서도 테스트 작성 방식은 자유롭게 확장할 수 있게 설계한 것입니다.
JUnit Jupiter
JUnit 5에서 새로 제공하는 모듈로, 최신 자바 기능과 다양한 테스트 기능을 제공합니다.
@Test, @BeforeEach, @AfterEach 등의 어노테이션과 Assertions, Assumptions 등 다양한 기능을 포함하고 있으며 이를 통해 개발자는 유지보수하기 쉬운 테스트 코드를 작성할 수 있습니다.
JUnit Jupiter를 분리한 이유
Jupiter는 JUnit 5의 새로운 테스트 작성 모델입니다.
JUnit 4는 기능 확장에 한계가 있었고, 최신 자바 기능을 충분히 활용하기 어려웠습니다. 그래서 기존 구조와 분리하여 완전히 새로운 프로그래밍 모델로 재설계했습니다.
이렇게 분리함으로써 JUnit은 과거 제약에서 벗어나 파라미터 테스트, 동적 테스트, 조건부 실행 등 현대적인 테스트 기능을 자유롭게 발전시킬 수 있게 되었습니다.
JUnit Vintage
JUnit Vintage는 JUnit 3나 JUnit 4로 작성된 레거시 테스트를 JUnit 5 플랫폼에서 실행할 수 있도록 지원하는 모듈입니다.
이를 통해 기존 테스트를 새 프레임워크로 옮기는 과정에서도 하위 호환성을 보장하며, 단계적으로 JUnit 5로 마이그레이션할 수 있습니다.
JUnit Vintage를 분리한 이유
실무 프로젝트에는 이미 수많은 JUnit 3, 4 테스트 코드가 존재합니다.
만약 JUnit 5로 전환하는 순간 기존 테스트가 모두 동작하지 않는다면,
현실적으로 도입이 어려웠을 것입니다.
Vintage는 이러한 문제를 해결하기 위해 만들어졌습니다.기존 테스트를 그대로 실행할 수 있도록 하여, 점진적인 마이그레이션을 가능하게 했습니다.
즉, 기술적 진화와 현실 사이의 균형을 맞춘 설계라고 볼 수 있습니다.
Third Party
Spock, TestNG 등 다른 테스트 라이브러리와의 통합을 지원합니다.
Third Party를 지원하도록 분리한 이유
JUnit 5는 단순한 테스트 도구가 아니라 다양한 테스트 엔진이 올라갈 수 있는 실행 플랫폼을 지향합니다.
Platform 위에 Jupiter뿐 아니라, 외부 라이브러리나 다른 테스트 프레임워크도 연결할 수 있도록 설계되었습니다.
이를 통해 JUnit은 폐쇄적인 구조가 아니라 확장 가능한 생태계를 갖춘 테스트 플랫폼으로 발전하게 되었습니다.
등장 배경
기존 JUnit 4는 기능이 제한적이고 확장성이 부족했습니다.
특히 최신 자바 기능을 활용하거나 다양한 테스트 요구 사항을 충족하기에는 불편한 점이 많았습니다.
이에 따라 JUnit 5는 2017년 이후 등장했으며, 모듈화 구조(Jupiter, Vintage, Platform)를 통해 최신 기능과 호환성을 강화하고, 다른 라이브러리와의 연동성을 높였습니다.
JUnit 5의 장점
감사 로그나 시스템 로그를 기록할 때 사용합니다. 로그인 시도, API 호출 이력, 주문 시도 같은 것들은 메인 로직이 실패해도 기록으로 남겨야 합니다. 알림 발송 이력도 마찬가지입니다. 이메일을 보냈다는 사실 자체는 주문이 취소되더라도 이력으로 남아있어야 나중에 추적할 수 있습니다. 카운터나 통계 업데이트에도 적합합니다. 방문자 수, 조회 수, 다운로드 횟수 같은 것들은 메인 작업의 성공 여부와 무관하게 증가해야 합니다. 또한 배치 작업에서 각 아이템의 처리 결과를 독립적으로 저장할 때도 사용합니다.
JUnit 5의 단점
기존 JUnit 4에 익숙한 개발자라면 처음 학습할 때 구조와 어노테이션 사용법을 학습하는 비용이 발생합니다.
또한 JUnit 자체는 Mocking 기능을 제공하지 않기 때문에, 외부 의존성을 대체하거나 특정 동작을 시뮬레이션하려면 Mockito 같은 라이브러리를 별도로 사용해야 합니다.
마치며
이번 글을 작성하면서 Mockito와 JUnit 5, 그리고 Mock, Stub, Spy 개념을 다시 한 번 정리할 수 있었습니다.
테스트 코드를 단순히 작성하는 것과 왜 이런 라이브러리가 필요한지, 어떤 장단점이 있는지를 이해하며 작성하는 것은 큰 차이가 있다는 걸 다시 한번 느꼈습니다.
글을 작성하면서 예전에 테스트가 왜 실패하는지 이유를 몰랐던 것이, 위의 Mock, Stub, Spy 개념이 제대로 잡혀 있지 않았기 때문이라는 걸 깨달았습니다.
이전에는 Mockito를 생각 없이 사용했지만, 이번 계기로 Mockito를 활용하면 테스트 속도와 안정성을 크게 높일 수 있고, 의도한 행위를 검증하면서도 외부 시스템에 의존하지 않는 장점이 있다는 것을 다시 한 번 알게 되었습니다.
이런 경험을 통해 단순한 테스트 코드 작성이 아니라, 개념과 원리를 이해하고 목적에 맞게 사용하는 것이 진짜 실력있는 개발자의 역량이라는 생각이 들었습니다.
아직 배워야 할 것도 많고 모르는 부분도 많지만, 이번 정리를 통해 Mockito와 JUnit 5를 보다 목적에 맞게 활용할 수 있는 기반을 갖출 수 있었다는 점에서 의미가 있었습니다.
앞으로는 테스트를 단순히 통과시키는 것이 아니라 안정적이고 예측 가능한 테스트를 작성하는 습관을 계속해서 만들어가려고 합니다.
'스프링' 카테고리의 다른 글
| Dynamic Proxy vs CGLIB 방식 차이 (0) | 2026.02.21 |
|---|---|
| 스프링 트랜잭션(@Transactional) 전파레벨 7가지 (0) | 2026.02.13 |
| FIRST 원칙으로 바라본 테스트 코드 작성법 (0) | 2026.01.30 |
| Spring Validation 완전 정복 - 올바른 유효성 검증 전략 (0) | 2026.01.29 |
| 스프링에서 스코프(Scope)란 무엇인가? (0) | 2026.01.19 |