-
Rest APIλ 무μμ΄λ©°, RestFul νλ€λ 건 μ΄λ€ μλ―ΈμΈκ°?
REST APIμ RESTfulμ κ°λ μ μΉ κ°λ°μμ μμ£Ό λ±μ₯νμ§λ§, μ²μ μ νλ©΄ ν·κ°λ¦΄ μ μμ΄μ. μ½κ² νμ΄μ μ€λͺ ν΄λ³Όκ²μ.
πΉ REST APIλ?
RESTλ “REpresentational State Transfer"μ μ½μμμ. 2000λ μ λ‘μ΄ νλ©(Roy Fielding)μ΄ λ Όλ¬Έμμ μ μν μΉ μν€ν μ² μ€νμΌ μ€ νλμ£ .
μ¦, REST APIλ μ΄ REST μμΉμ λ°λΌ μ€κ³λ μΉ APIλ₯Ό λ§ν΄μ.
πΉ RESTμ ν΅μ¬ κ°λ
RESTλ λ€μμ 6κ°μ§ μ μ½ μ‘°κ±΄μ κΈ°λ°μΌλ‘ ν΄μ:
-
ν΄λΌμ΄μΈνΈ-μλ² κ΅¬μ‘°
- ν΄λΌμ΄μΈνΈ(λΈλΌμ°μ , μ±)μ μλ²λ μλ‘ λΆλ¦¬λμ΄ μν μ λλ.
-
무μνμ± (Stateless)
CreatedThu, 03 Apr 2025 18:02:29 +0900 -
-
@Mockκ³Ό@MockBeanμ λ λ€ ν μ€νΈμμ Mock κ°μ²΄λ₯Ό λ§λ€κΈ° μν μ΄λ Έν μ΄μ μ΄μ§λ§, μ°μ΄λ νκ²½κ³Ό μν μ΄ μ‘°κΈ λ¬λΌμ. κ°λ¨ν μ°¨μ΄λΆν° μμ½νλ©΄:
πΉ
@Mock(from Mockito)- Mockitoμμ μ 곡νλ μ΄λ Έν μ΄μ
- **μμ λ¨μ ν μ€νΈ(Unit Test)**μ μ¬μ©
- Spring Contextμλ 무κ΄
- ν μ€νΈ ν΄λμ€ λ΄μμ μμ‘΄μ±μ MockμΌλ‘ μ£Όμ νκ³ μΆμ λ μ¬μ©
MockitoAnnotations.openMocks(this)λλ@ExtendWith(MockitoExtension.class)μ ν¨κ» μ¬μ©ν΄μΌ ν¨
μμ:
@ExtendWith(MockitoExtension.class) class MyServiceTest { @Mock private MyRepository myRepository; @InjectMocks private MyService myService; @Test void testSomething() { // myRepositoryλ mock κ°μ²΄ } }
πΈ
@MockBean(from Spring Boot)- Spring Boot Testμμ μ 곡νλ μ΄λ Έν μ΄μ
- ν΅ν© ν μ€νΈ(Integration Test) μ μ¬μ©
- Spring ApplicationContextμ λ±λ‘λ Beanμ MockμΌλ‘ κ΅μ²΄
@SpringBootTest,@WebMvcTest,@DataJpaTestκ°μ Spring κΈ°λ° ν μ€νΈ νκ²½μμ μ¬μ©
μμ:
CreatedThu, 03 Apr 2025 15:57:29 +0900 -
@Mock κ³Ό @InjectedMock
@Mockκ³Ό@InjectMocksμ μ°¨μ΄λ₯Ό μμλ³Όκ²μ. μ΄ λμ ν¨κ» μμ£Ό μ°μ΄μ§λ§ μλ‘ λ€λ₯Έ μν μ ν΄μ.
πΉ
@Mock- Mockitoμμ μ 곡
- Mock κ°μ²΄λ₯Ό μμ±ν΄μ€
- λ¨λ μΌλ‘λ κ·Έλ₯ κ°μ§ κ°μ²΄μΌ λΏ, μ무 λ°λ μ£Όμ λμ§ μμ
@Mock private MyRepository myRepository; // κ°μ§ κ°μ²΄ μμ±
πΈ
@InjectMocks- μμ Mockitoμμ μ 곡
- ν
μ€νΈ λμ κ°μ²΄(ν
μ€νΈν ν΄λμ€)λ₯Ό μμ±νκ³ ,
κ·Έ μμ μλ μμ‘΄μ± νλμ@MockμΌλ‘ λ§λ κ°μ²΄λ€μ μλμΌλ‘ μ£Όμ
@InjectMocks private MyService myService; // MyService κ°μ²΄λ₯Ό λ§λ€κ³ , λ΄λΆμ μλ MyRepository λ±μ @Mock κ°μ²΄λ₯Ό μ£Όμ
β κ°λ¨ μμ
@ExtendWith(MockitoExtension.class) class MyServiceTest { @Mock private MyRepository myRepository; @InjectMocks private MyService myService; @Test void testSomething() { // myServiceλ μ€μ κ°μ²΄ // myRepositoryλ mock κ°μ²΄ // myService λ΄λΆμμ myRepositoryκ° μ¬μ©λ λ mockμ΄ μ£Όμ λ¨ } }
π μ 리νμλ©΄:
μ΄λ Έν μ΄μ μν @Mockκ°μ§(Mock) κ°μ²΄ μμ± @InjectMocksν μ€νΈ λμ κ°μ²΄λ₯Ό μμ±νκ³ , @Mockκ°μ²΄λ₯Ό κ·Έ μμ μ£Όμμ¦,
@Mockμ΄ κ°μ§ λΆνμ λ§λλ κ±°κ³ ,
@InjectMocksλ κ·Έ λΆνμ 쑰립ν΄μ ν μ€νΈ λμ κ°μ²΄λ₯Ό λ§λλ κ±°μμ.CreatedThu, 03 Apr 2025 15:57:29 +0900 -
Mock, Stub μ μ°¨μ΄
mockκ³Όstubμ ν μ€νΈμμ μΈλΆ μμ‘΄μ±μ λ체νκΈ° μν΄ μ°μ΄λ κ°μ²΄μΈλ°, λμ λͺ©μ κ³Ό κΈ°λ₯ λ©΄μμ μ‘°κΈ λ¬λΌμ.
π μμ½ λΉκ΅
νλͺ© Stub Mock λͺ©μ κ³ μ λ κ° λ°ν (λ¨μ λ체) λμ κ²μ¦ + μ μ°ν μ€μ κΈ°λ₯ 미리 μ μλ μλ΅ μ 곡 νΈμΆ μ¬λΆ, νΈμΆ νμ, νλΌλ―Έν° κ²μ¦ λ± κ°λ₯ ν μ€νΈ νμ μ£Όλ‘ μν κΈ°λ° ν μ€νΈ (State-based) μ£Όλ‘ νμ κΈ°λ° ν μ€νΈ (Behavior-based) μμ “μ΄ λ©μλκ° νΈμΆλλ©΄ 무쑰건 μ΄ κ°μ λ¦¬ν΄” “μ΄ λ©μλκ° μ νν ν λ² νΈμΆλλμ§ νμΈνμ”
πΉ Stub: λ¨μνκ² λ¦¬ν΄κ°λ§ μ ν΄λμ κ°μ§
- μ€μ λ©μλ λ‘μ§μ νμ μκ³ , “μ΄ κ°μ μ£Όλ©΄ μ΄ κ°μ λλ €μ€!” κ°μ κ°λ¨ν λ체ν.
- μ£Όλ‘ “κΈ°μ‘΄ μμ€ν μμ μ΄κ±°λ§ μ λλ‘ λμνλ©΄ λΌ"λΌλ ν μ€νΈμ μ¬μ©.
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
πΈ Mock: νμ(νΈμΆ μ¬λΆ λ±)κΉμ§ νμΈνλ κ°μ§
- 리ν΄κ°λ μ μν μ μμ§λ§, “μ λ§λ‘ μ΄ λ©μλκ° νΈμΆλλκ°?” κ°μ κ²μ¦λ κ°λ₯.
verify()κ°μ λ©μλλ₯Ό ν΅ν΄ ν μ€νΈμ μ νμ±μ λμ.
verify(userRepository, times(1)).findById(1L);
β μ€μ ν μ€νΈ μμ
@Mock UserRepository userRepository; @InjectMocks UserService userService; @Test void testUserFetch() { // Stub: 리ν΄κ° μ§μ when(userRepository.findById(1L)).thenReturn(Optional.of(new User("Alice"))); // λ©μλ μ€ν User user = userService.getUser(1L); // Mock: νΈμΆ μ¬λΆ κ²μ¦ verify(userRepository, times(1)).findById(1L); }
π κ°λ¨ λΉμ
- Stub: βλκ° μλ 무쑰건 μ΄ λμ¬λ§ λ§ν΄!β
- Mock: βλκ° μ€λ©΄ μ΄ λμ¬λ₯Ό νκ³ , μ λλ‘ λ§νλμ§ κ²μ¬λ ν©λλ€.!β
CreatedThu, 03 Apr 2025 15:57:29 +0900 -
ν μ€νΈ λλΈ(Test Double) μ©μ΄ μ 리
- μλλ ν μ€νΈμμ μμ£Ό μ°μ΄λ 5κ°μ§ ν μ€νΈ λμμ λλ€:
π§ͺ ν μ€νΈ λλΈ(Test Double) μ’ λ₯ μμ½
νμ μ€λͺ μ£Όλ‘ νλ μΌ λν μμ Dummy κ°μ΄ νμνμ§λ§ μ무 μν λ νμ§ μλ κ°μ²΄ λ©μλ μκ·Έλμ² μ±μ°κΈ° μ© new User(null)μ²λΌ κ°λ§ μ±μFake μ€μ ꡬνμ κ°κΉμ°λ, λ¨μνκ±°λ in-memory κΈ°λ° κ°λ¨ν λ체 ꡬν InMemory DB, FakeEmailSender Stub κ³ μ λ μλ΅μ 리ν΄νλ κ°μ²΄ μν κΈ°λ° ν μ€νΈ when(repo.findById()).thenReturn()Mock νμ κΈ°λ°, νΈμΆ μ¬λΆλ μμ κ²μ¦μ΄ κ°λ₯ν κ°μ²΄ νμ κΈ°λ° ν μ€νΈ verify(repo).save()Spy μ€μ κ°μ²΄λ₯Ό κ°μΈλ©΄μ, λΆλΆμ μΌλ‘ mock κ°λ₯ μ€μ λμ + νΈμΆ κ°μ spy(new UserService(...))
1. π£ Dummy
- κ°μ₯ λ¨μν νν.
- μ무 μΌλ νμ§ μκ³ , κ°λ§ μ±μ°λ μ©λ.
- ν μ€νΈμ μ§μ μ μλ―Έ μμ. μμ΄λ ν μ€νΈ ν΅κ³Ό κ°λ₯.
User dummyUser = new User(null); // μμ±μ λ§€κ°λ³μ λ§μΆλ €κ³ λ£λ μ©λ
2. π§ͺ Stub
- νΈμΆνλ©΄ μ ν΄μ§ κ°μ 리ν΄ν¨.
- λμ μ체λ λ¨μνκ³ κ²μ¦ λ‘μ§μ μμ.
- μν κΈ°λ° ν μ€νΈμ μ¬μ©λ¨.
when(userRepository.findById(1L)).thenReturn(Optional.of(new User("Alice")));
3. π€ Mock
- Stubμ²λΌ 리ν΄κ°μ μ€ μ μμ§λ§, μΆκ°λ‘ νΈμΆ μ¬λΆλ κ²μ¦ν¨.
- ν μ€νΈμ **νμ κ²μ¦(Behavior Verification)**μ μ΄μ .
verify(userRepository, times(1)).save(any());
4. π΅οΈ Spy
- μ€μ κ°μ²΄λ₯Ό κ°μΈλ©΄μ, νμν λΆλΆλ§ mock μ²λ¦¬ κ°λ₯.
- μΌλΆ λ©μλλ μ§μ§ μ€ν, μΌλΆλ stub/mock κ°λ₯.
- μ¬μ© μ μ‘°μ¬: μ§μ§ μμ‘΄μ±μ΄ λ°λΌμ¬ μ μμ.
UserService spyService = spy(new UserService(realRepository)); doReturn("fakeName").when(spyService).getUserName();
5. π§© Fake
- μ§μ§ ꡬν체λ μλμ§λ§, μ€μ λ‘ λμνλ κ°λ¨ν ꡬν.
- ν μ€νΈμ©μΌλ‘ κ°λ³κ² λ§λ μ§μ§ λΉμ¦λμ€ λ‘μ§μ νλ΄λΈ κ².
- μ: InMemory DB, ν μ€νΈμ© λ©μμ§ ν λ±
public class InMemoryUserRepository implements UserRepository { private Map<Long, User> db = new HashMap<>(); public Optional<User> findById(Long id) { return Optional.ofNullable(db.get(id)); } }
π― μ΄λ€ κ±Έ μΈμ μ¨μΌ ν κΉ?
ν μ€νΈ λͺ©μ μ ν©ν λλΈ νλΌλ―Έν° μ±μ°κΈ°λ§ νλ©΄ λ¨ Dummy κ³ μ λ κ° λ¦¬ν΄λ§ νμ Stub νΈμΆ μ¬λΆ/νμ κ²μ¦ Mock μ€μ κ°μ²΄ + μΌλΆ mocking Spy μ€μ ꡬν λΉμ·ν λμ νμ Fake
Fake Repository μμ
Spring νκ²½μμ μμ£Ό μ°μ΄λ Fake Repository μμ λ₯Ό 보μ¬λ릴κ²μ.
λ¨μ ν μ€νΈμμ JPA κ°μ μ€μ DB μ°κ²° μμ΄λ λ‘μ§μ κ²μ¦νκ³ μΆμ λ μμ£Ό μ μ©ν©λλ€.CreatedThu, 03 Apr 2025 15:57:29 +0900 -
MSA(Microservices Architecture)μμ trace IDμ span IDλ **λΆμ° μΆμ (distributed tracing)**μμ μ¬μ©λλ κ³ μ μλ³μλ€μ΄μΌ. μμ€ν μ 체μμ μμ²μ νλ¦μ μΆμ ν μ μκ² ν΄μ£Όλ ν΅μ¬ κ°λ μ λλ€.
1. Trace ID
- νλμ μ 체 μμ²(Request)μ μλ³νλ κ³ μ ν IDμ λλ€.
- μ: μ¬μ©μκ° μΉμμ μ΄λ€ λ²νΌμ ν΄λ¦ν΄μ API μμ²μ΄ λ€μ΄μ€κ³ , κ·Έ μμ²μ΄ μ¬λ¬ κ°μ λ§μ΄ν¬λ‘μλΉμ€λ₯Ό κ±°μ³ μ²λ¦¬λλ κ²½μ°, μ΄ μ 체 νλ¦μ νλμ traceλ‘ λ΄ λλ€.
- μ¦, trace IDλ **“μ΄ μμ²μ΄ μ΄λμ μμλΌμ μ΄λκΉμ§ κ°λκ°”**λ₯Ό λνλ.
2. Span ID
- trace μμμ **κ°κ°μ μμ λ¨μ(μμ νΈμΆ νλνλ)**λ₯Ό μλ³νλ IDμ λλ€.
- μ: A μλΉμ€κ° B μλΉμ€μ C μλΉμ€λ₯Ό νΈμΆνλ©΄, A-B, A-C κ°κ°μ΄ λ³λμ spanμ΄ λκ³ , κ°κ°μ λν span IDκ° μμ΅λλ€.
- Spanμ μμ μκ°, μ’ λ£ μκ°, μ΄λ€ μμ μΈμ§ λ± μΈλΆ μ 보λ ν¬ν¨λ©λλ€.
ꡬ쑰 μμ
[Trace ID: abc123] βββββββββββββββ βββββββββββββββ β User RequestββββββββΆβ API Gateway βββββ βββββββββββββββ βββββββββββββββ β βΌ ββββββββββββββ β Service A βββββ ββββββββββββββ β βΌ ββββββββββββββ β Service B β ββββββββββββββ- μ 체 μμ²μ νλ¦μ΄ νλμ Trace ID (
abc123)λ₯Ό 곡μ νκ³ , - κ° μλΉμ€ κ° νΈμΆμ μλ‘ λ€λ₯Έ Span IDλ₯Ό κ°μ§.
μμ½
νλͺ© μ€λͺ Trace ID μ 체 μμ² νλ¦μ μΆμ νκΈ° μν κ³ μ ID Span ID Trace λ΄μμ κ°λ³ μμ μ ꡬλΆνλ ID
첨μΈ
- μ€μ MSA ꡬνμ front endμμ api requestμμ λΆν° trace idμ span idκ° μ±λ²λκ³
- μλ₯Ό λ€μ΄, bff -> api-gateway -> user-service λ₯Ό ν΅ν΄ request κ° νλ¬κ°λ€λ©΄, κ° μλΉμ€λ λμΌν trace idλ‘ μΆμ μ΄ λμ΄μΌ ν©λλ€.
CreatedWed, 02 Apr 2025 10:50:33 +0900