λ°°κ²½
μ ν¬λ νλ‘μ νΈ μ΄κΈ°λΆν° ν
μ€νΈ μλλ₯Ό λμ΄κΈ° μν΄ μ κ²½μ μΌμ΅λλ€. λνμ μΌλ‘ ν
μ€νΈ λ°μ΄ν°λ₯Ό 격리νκΈ° μν΄ @DirtiesContext λ₯Ό μ¬μ©νμ§ μκ³ , sql script λ₯Ό μ΄μ©νμμ΅λλ€. λν μλΉμ€ κ³μΈ΅μμ λΆνμν @SpringBootTest λμ @DataJpaTest μ @Import λ§μΌλ‘ μλΉμ€ κ³μΈ΅ ν
μ€νΈλ₯Ό μνν μ μλλ‘ νμμ΅λλ€.
νμ§λ§ νλ‘μ νΈκ° μ μ 컀μ§κ³ ν
μ€νΈ μΌμ΄μ€ μκ° 400κ°λ₯Ό λμ΄κ°λ©΄μ, ν
μ€νΈλ₯Ό μννλ λ°μ 걸리λ μκ°μ΄ μ μ μ€λ κ±Έλ¦¬κ² λμμ΅λλ€. μμλλ μκ°μ΄ μ μ§μ μΌλ‘ λμ΄λ¬κΈ°λλ¬Έμ μΈμ§νκΈ° νλ€μλλ°, νλ²μ ν
μ€νΈλ₯Ό λλ¦¬κ³ νμ₯μ€μ λ€λ
μλλ°λ μ 체 ν
μ€νΈκ° κ³μ μνμ€μ΄μλ κ²μ λ³΄κ³ βμ΄κ±° λ무 μ€λ 걸리λλ°βλ₯Ό κΉ¨λ«κ² λμμ΅λλ€..
νμ¬ κΈ°μ€μΌλ‘ μ°νλ μκ°μ 51μ΄ μ λ λμ§λ§ μ€μ μΆμ°λμ§ μμ μκ°κΉμ§ ν©νλ©΄ μ²΄κ° μκ°μ 2λΆ μ λ 걸리λ κ² κ°μ΅λλ€. νμλ€λ λμΌν λ¬Έμ λ₯Ό μΈμνκ³ μμκΈ°λλ¬Έμ ν
μ€νΈ μλλ₯Ό κ°μ νκΈ°λ‘ κ²°μ νμ΅λλ€.
μμΈ νμ
λ¨Όμ μ΄λμ μ€λ 걸리λμ§ νμ
νλ κ²μ΄ μ€μνμ΅λλ€. μ ν¬ ν
μ€νΈλ ν¬κ² μ΄ 5κ°μ§ μΈμ ν
μ€νΈ, 컨νΈλ‘€λ¬ ν
μ€νΈ, μλΉμ€ ν
μ€νΈ, λ ν¬μ§ν 리 ν
μ€νΈ, λλ©μΈ λ¨μ ν
μ€νΈλ‘ ꡬλΆν μ μμ΅λλ€. λλ©μΈ λ¨μ ν
μ€νΈλ λ¨μν μ»΄νμΌλ§ λλ©΄ μνν μ μμ§λ§ λλ¨Έμ§ 4κ°μ§μ ν
μ€νΈλ 컨ν
μ€νΈ λΉμ λμμΌ ν©λλ€. μ΄ μ€ μΈμ ν
μ€νΈ λ @SpringBootTestλ₯Ό μ¬μ©νκ³ , 컨νΈλ‘€λ¬ ν
μ€νΈλ @WebMvcTest, μλΉμ€ ν
μ€νΈμ λ ν¬μ§ν 리 ν
μ€νΈλ @DataJpaTest λ₯Ό μ¬μ©νμ¬ μ»¨ν
μ€νΈ λΉλ€μ λμλλ€. κ·Έλ¦¬κ³ μ΄ μ»¨ν
μ€νΈ λΉμ λμ°λ κ³Όμ μμ μκ°μ΄ μμλ κ²μ΄λΌκ³ νλ¨νμμ΅λλ€. μ€μ λ‘ ν
μ€νΈκ° μνλλ μ°½μ λ³΄κ³ μμΌλ©΄ λλ©μΈ λ¨μ ν
μ€νΈ λΆλΆμ βμ§μ§ ν
μ€νΈλ₯Ό ν건κ°β μΆμ μ λλ‘ μμκ°μ μ§λκ°μ§λ§, λλ¨Έμ§ ν
μ€νΈλ€μ βμ μ§κΈ μ΄ ν
μ€νΈλ₯Ό μννλ μ€μ΄κ΅¬λβ λΌλ μκ°μ΄ λ€ μ λλ‘ λ²νΌλ§μ΄ μμμ΅λλ€.
Application Context Caching
ν
μ€νΈκ° μνλλ κ±Έ 보λλ° μ΄μν μ μ΄ λ°κ²¬λμμ΅λλ€. SpringBoot κ° μ¬μ€νλλ λΉλκ° λ무 λμμ΅λλ€.
@DirtiesContext λ₯Ό μ¬μ©νμ§ μμ μ΄μ κ° ν
μ€νΈ νκ²½ μ΄κΈ°ν(μ ννλ in-memory DB μ΄κΈ°ν)λ₯Ό ν
μ€νΈλ§λ€ λ§€λ² νμ§ μκ³ Application Context λ₯Ό μ¬νμ©νκΈ° μν΄μμλλ°, Application Context λ₯Ό μ¬νμ©νμ§ μκ³ SpringBoot κ° κ³μ μ¬μ€νλλ κ²μ΄μμ΅λλ€.
μ΄μ λ Application Context Caching μ μ¬νμ© μ‘°κ±΄μ μμμ΅λλ€. Application Context Caching μ΄ μ¬νμ©λμ§ μκ³ μλ‘ λμ°λ 쑰건μ
1.
λ±λ‘λ λΉλ€ μ€ λΉμ λ΄λΆμμ μ€μΌμ΄ λμμ λ(λΉμ λ΄λΆ νλκ° λ³κ²½λμμ λ)
2.
Context λ΄λΆ λ±λ‘λ λΉμ λ΄μ©μ΄ λ€λ₯Ό λ(λΉ μΆκ° λλ μ κ±°)
λ±μ΄ μλλ°, 2λ²μμ λ¬Έμ κ° λ°μνμμ΅λλ€. 2λ²μ μ½κ² λ§ν΄μ Context Bean λ€μ μ€λ
μ·μ λ¨κ³ , μ΄ν ν
μ€νΈμμ Context Bean μ λ΄μ©μ΄ μ€λ
μ·κ³Ό λ€λ₯΄λ©΄ Caching μ μ¬μ©νμ§ μκ³ λ€μ μ΄κΈ°νμν€λ κ²μ
λλ€. μ ν¬κ° μμ±ν @WebMvcTest μ λ΄μ©μ κ°λ¨ν λ€μκ³Ό κ°μ΅λλ€.
@MockBean(RoleService.class)
@WebMvcTest(RoleController.class)
class RoleControllerTest {
}
@MockBean(AppointmentService.class)
@WebMvcTest(AppointmentController.class)
class AppointmentControllerTest {
}
Java
볡μ¬
RoleControllerTest λ₯Ό μνν λμλ @WebMvcTest μ κ΄λ ¨λ λΉ, RoleService, RoleController κ° Application Context μ λΉμΌλ‘ λ±λ‘λκ³ , AppointmentControllerTest λ₯Ό μνν λμλ @WebMvcTest μ κ΄λ ¨λ λΉ, AppointmentService, AppointmentController κ° λΉμΌλ‘ λ±λ‘μ΄ λ©λλ€. μ΄μ λ°λΌ μΊμ±μ 쑰건μ μλ°°νκ² λκ³ , 컨ν
μ€νΈ λΉμ μλ‘ λμ°κ² λλ κ²μ΄μμ΅λλ€.
μ΄λ¬ν λ¬Έμ λ 컨νΈλ‘€λ¬ ν
μ€νΈ λΏλ§ μλλΌ μλΉμ€ ν
μ€νΈμμλ λ°κ²¬λμμ΅λλ€.
@DataJpaTest
@Import(PollService.class)
@Sql(scripts = {"classpath:schema.sql", "classpath:data.sql"})
class PollServiceTest {
private final MemberRepository memberRepository;
private final TeamRepository teamRepository;
private final TeamMemberRepository teamMemberRepository;
private final PollService pollService;
@Autowired
public PollServiceTest(
MemberRepository memberRepository,
TeamRepository teamRepository,
TeamMemberRepository teamMemberRepository,
PollService pollService
) {
this.memberRepository = memberRepository;
this.teamRepository = teamRepository;
this.teamMemberRepository = teamMemberRepository;
this.pollService = pollService;
}
}
Java
볡μ¬
ServiceTest λ μμ κ°μ΄ ꡬμ±λμ΄μκ³ , κ°κ°μ ServiceTest λ₯Ό μνν λλ§λ€ @DataJpaTest μ κ΄λ ¨λ λΉ, @Import λ‘ λ±λ‘νλ κ°κ°μ Service κ° λΉμΌλ‘ λ±λ‘μ΄ λ©λλ€. μ΄μ λ°λΌ 맀 μλΉμ€ ν
μ€νΈ ν΄λμ€λ§λ€ Application Context λ₯Ό μ΄κΈ°νμμΌ°μ΅λλ€.
SpringBoot κ° μΌλ§λ λ§μ΄ μ€νλλμ§ μ€μ λ‘ μΈμ΄λ³΄μμ΅λλ€.
β’
appointment, auth, poll, role, team λ± 5κ° λλ©μΈμ μλΉμ€ ν
μ€νΈ, 컨νΈλ‘€λ¬ ν
μ€νΈ μμ 10λ²
β’
core 1κ° λλ©μΈμμ μλΉμ€ ν
μ€νΈ, λκ°μ 컨νΈλ‘€λ¬ ν
μ€νΈ μ΄ 3λ²
β’
notification 1κ° λλ©μΈμμ λ κ°μ μλΉμ€ ν
μ€νΈ μ΄ 2λ²
β’
μΈμν
μ€νΈ, ApplicationTest λ₯Ό μν @SpringBootTest μμ μ΄ 1λ²
β’
λ ν¬μ§ν 리 ν
μ€νΈλ₯Ό μν @DataJpaTest μμ μ΄ 1λ²
μ 체 ν
μ€νΈλ₯Ό νλ² λ리면 μ΄ 17λ² Application Context κ° μ΄κΈ°νλκ³ μμμ΅λλ€
ν΄κ²° - λμΌν νκ²½μ λ§λ€μ!
κ°μ μ±κ²©μ ν
μ€νΈλ§λ€, κ°μ Application Context Bean νκ²½μ λ§λ€μ΄μ£Όλ©΄ Caching μ΄ λμ΄ μ΄κΈ°ννκΈ° μν SpringBoot μ¬μ€νμ νμ§ μμ κ²μ΄λΌκ³ μκ°νμμ΅λλ€.
1. λͺ¨λ SpringBootTest λ‘ νμ!
κ°μ₯ μ²μ μκ°ν λ°©λ²μ λͺ¨λ λΉλ€μ λμ SpringBoot λ₯Ό νλ²λ§ μ€νμν€λ κ²μ΄μμ΅λλ€. νμ§λ§ μ΄ λ°©λ²μ λ¨μν SpringBoot λ₯Ό νλ²λ§ μ€νμν€λ κ² λΏμ΄μ§, κ°κ°μ ν
μ€νΈ μ±κ²©μ λ§μ§ μλ λ°©λ²μ΄μμ΅λλ€. λ§€λ² μ 체 ν
μ€νΈλ§ λλ¦¬μ§ μμ κ²μ΄κ³ , ν
μ€νΈ ν΄λμ€λ§ λ¨λ
μΌλ‘ λ리λ μΌλ λ§μλ° λΆνμν λΉλ€μ΄ λ±λ‘λμ΄ μκΈ°λλ¬Έμ μ 체μ μΈ ν
μ€νΈ λ§₯λ½μ 보면 μ΄μΈλ¦¬μ§ μλ ν΄κ²°μ±
μ΄μμ΅λλ€.
2. μ μ¬ν μ±κ²©μ λΉλ€μ νλ²μ λ±λ‘νμ!
μ μ¬ν 보면 ν΄λΉ λ¬Έμ λ 컨νΈλ‘€λ¬ ν
μ€νΈμ μλΉμ€ ν
μ€νΈμμ λ°μνλ λ¬Έμ μ
λλ€. 컨νΈλ‘€λ¬ ν
μ€νΈμμλ κ°κ° λλ©μΈμ 컨νΈλ‘€λ¬μ λͺ¨νΉμ μν μλΉμ€ ν΄λμ€λ§ λΉμΌλ‘ λ±λ‘μ΄ λλ©΄ ν
μ€νΈ μνμ΄ κ°λ₯νκ³ , μλΉμ€ ν
μ€νΈλ μλΉμ€ ν΄λμ€λ§ λΉμΌλ‘ λ±λ‘μ΄ λλ©΄ ν
μ€νΈ μνμ΄ κ°λ₯ν©λλ€.
μ΄μ λ°λΌ μΆμνλ₯Ό νλ¨κ³ λμ΄κ³ μΆκ°λλ ν΄λμ€λ§ νλ²μ λΉμΌλ‘ λ±λ‘νλ λ°©λ²μ μκ°νμ΅λλ€.
@WebMvcTest({
GlobalController.class,
OAuthController.class,
TeamController.class,
PollController.class,
AppointmentController.class,
RoleController.class,
NotificationController.class,
})
@MockBean({
OAuthService.class,
TeamService.class,
AppointmentService.class,
PollService.class,
RoleService.class,
WebhookService.class
})
public abstract class ControllerTest {
}
Java
볡μ¬
λ€μκ³Ό κ°μ΄ μμν μΆμ ν΄λμ€λ₯Ό νλ λ§λ€κ³ , ν΄λΉ ν΄λμ€μμ 컨νΈλ‘€λ¬μ μλΉμ€ ν΄λμ€λ€μ λͺ¨λ νλ²μ λ±λ‘μμΌ°μ΅λλ€. μ΄λ¬ν λ°©μμΌλ‘ μΈ κ°μ§μ μ₯μ μ μ»μ μ μμμ΅λλ€.
1.
κ°κ°μ λλ©μΈμμ 컨νΈλ‘€λ¬ ν
μ€νΈλ₯Ό μνν λ κ΄λ ¨λ λͺ¨λ λΉλ€μ΄ λ±λ‘λμ΄ μΆκ°, λ³κ²½λλ λΉλ€μ΄ μκΈ°λλ¬Έμ λμΌν 컨ν
μ€νΈ λΉ νκ²½μ λ§λ€ μ μλ€.
2.
컨νΈλ‘€λ¬ ν
μ€νΈμμ 곡ν΅μΌλ‘ μνλλ μΈμ¦ κ΄λ ¨ λ‘μ§(JwtProvider)μ λ§λ€μ΄ μ€λ³΅λλ λ©μλλ μ κ±°ν μ μλ€.
3.
μλ‘μ΄ λλ©μΈμ λν 컨νΈλ‘€λ¬ ν
μ€νΈλ₯Ό μΆκ°ν λμλ, ν΄λΉ 컨νΈλ‘€λ¬λ₯Ό μ΄λ
Έν
μ΄μ
μ μΆκ°λ§ νλ©΄ λμ΄ νμ₯ μΈ‘λ©΄μμ κ΄λ¦¬κ° μ©μ΄νλ€.
κ·Έλ¦¬κ³ κ°κ°μ 컨νΈλ‘€λ¬ ν
μ€νΈμμ λΆμ΄μλ μ΄λ
Έν
μ΄μ
μ μ§μ°κ³ ControllerTest λ₯Ό μμλ°μμ΅λλ€.
class RoleControllerTest extends ControllerTest {
}
Java
볡μ¬
μλΉμ€ ν
μ€νΈλ μ μ¬νμ§λ§ λ€λ₯Έ λ°©μμΌλ‘ λ³κ²½νμμ΅λλ€. νμ§λ§ κ°κ°μ μλΉμ€μμ 곡ν΅λλ λ‘μ§μ΄ μμκΈ°λλ¬Έμ κ΅³μ΄ ν΄λμ€λ‘ μμνμ§ μκ³ μ»€μ€ν
μ΄λ
Έν
μ΄μ
μ ꡬννμμ΅λλ€.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@DataJpaTest(includeFilters = @Filter(classes = {Service.class, FakeBean.class}))
@Sql(scripts = {"classpath:schema.sql", "classpath:data.sql"})
public @interface ServiceTest {
}
Java
볡μ¬
includeFilters μ΅μ
μ ν΅ν΄ λΉμΌλ‘ λ±λ‘ν ν΄λμ€λ₯Ό μΆκ°ν μ μμ΅λλ€. ν΄λΉ μ΄λ
Έν
μ΄μ
μμλ μλΉμ€ μ΄λ
Έν
μ΄μ
μ΄ λΆμ ν΄λμ€λ₯Ό λΉμΌλ‘ λ±λ‘μμΌ°μ΅λλ€. FakeBean μ Slack λ±κ³Ό κ°μ μΈλΆ API λ‘μ§μ μν΄ μ¬μ©ν 컀μ€ν
μ΄λ
Έν
μ΄μ
μ΄κ³ , μλΉμ€ ν
μ€νΈμ μ¬μ©λμ΄ ν¨κ» μΆκ°νμμ΅λλ€.
μ΄μ κ°μ μ΄λ
Έν
μ΄μ
λ°©μμ μλ‘μ΄ λλ©μΈμ λν μλΉμ€κ° μΆκ°λμ΄λ, ν΄λΉ μλΉμ€ ν΄λμ€λ₯Ό λ°λ‘ ν
μ€νΈ ν΄λμ€μμ μΆκ°ν νμμμ΄ includeFilters λ₯Ό ν΅ν΄ λ±λ‘λκΈ°λλ¬Έμ 컨νΈλ‘€λ¬ ν
μ€νΈ λ°©μλ³΄λ€ νμ₯μ λμ± μ©μ΄νλ€λ μ₯μ μ΄ μμ΅λλ€.
Application Context νκ²½μ μμ κ°μ΄ λ³κ²½νκ³ λ€μ SpringBoot μ¬μ€ν νμλ₯Ό μΈμ΄λ³΄λ 5λ²μΌλ‘ μ€μμ΅λλ€. @SpringBootTest, @DataJpaTest, 컨νΈλ‘€λ¬ ν
μ€νΈ, μλΉμ€ ν
μ€νΈμμ ν λ²μ© μ΄ 4λ² μ¬μ€νλμκ³ , @ActiveProfiles(βmasterβ) μ΄λ
Έν
μ΄μ
μ΄ λΆμ PollSchedulerTest μμ μ¬μ€νμ νλ² λ νμμ΅λλ€. κ΄λ ¨ λ νΌλ°μ€λ₯Ό 보면 active profile μ΄ λ€λ₯Έ κ²½μ°μλ μ¬νμ©νμ§ μκ³ μλ‘ μ»¨ν
μ€νΈ λΉμ λμ΄λ€κ³ ν©λλ€.
μν μκ°μ΄ ν¬κ² μ€μ§λ μμμ§λ§ Application Context λ₯Ό μ΄κΈ°ννκΈ° μν΄ λλ λκΈ°λ μμ
μ΄ 17νμμ 5νλ‘ μ€μκ³ , μΆμ°λμ§ μμ 체κ°μ μκ°λ λ§μ΄ μ€μμ΅λλ€.
μ΄κΈ°νλλ μΉ΄μ΄νΈ μ΅μ μΆκ°
μ§μ μΈλ λ°©λ² μ΄μΈμλ springframework μ μ΅μ
μ μ΄μ©νμ¬ μ»¨ν
μ€νΈ λΉμ΄ μ¬νμ©λλ μλ₯Ό μΉ΄μ΄ν
ν μ μμ΅λλ€.
logging:
level:
org:
springframework:
test.context.cache: DEBUG
YAML
볡μ¬
springframework μ ContextCache λ μμ μ΄μ©ν Application Context Caching μ ν
μ€νΈμμ μ§μν΄μ£Όλ μΈν°νμ΄μ€μ
λλ€. ν΄λΉ ν΄λμ€μ λ‘κΉ
μ΅μ
μ DEBUGλ‘ μ€μ νλ©΄ logStatistics λ©μλλ₯Ό μ΄μ©νμ¬ ν΅κ³λ₯Ό νμΈν μ μμ΅λλ€.
[2022-10-22 15:26:51:34733][main] DEBUG o.springframework.test.context.cache - Spring test ApplicationContext cache statistics: [DefaultContextCache@436867c3 size = 5, maxSize = 32, parentContextCount = 0, hitCount = 1384, missCount = 5]
Plain Text
볡μ¬
missCount λ₯Ό νμΈνμ¬ Context caching μ νμ§ λͺ»νκ³ μ΄κΈ°νν νμλ₯Ό νμΈν μ μμ΅λλ€.
build λ°©μ λ³κ²½
μ½λλ₯Ό μμ νλ λ°©λ² μ΄μΈμ build λ₯Ό νλ λ°©μμμλ μλμ μ°¨μ΄κ° μλ€λ λ§μ λ£κ³ μ΄μ κ΄ν΄ μ°Ύμ보μμ΅λλ€. build μλν λꡬ μ€μλ gradle μ΄μΈμλ IntelliJ λ μμ΅λλ€. gradle μ μ€ν μμ€ λΉλ μλν λꡬμ΄κ³ IntelliJ μ build λ IntelliJ μ체μμ μ 곡νλ λΉλ μλν λꡬμ
λλ€.
λ λꡬλ build λ°©μμμ μ°¨μ΄κ° μλλ°, κ°μ₯ ν° μ°¨μ΄μ μ IntelliJ build μμ μ 곡νλ Incremental Build μ
λλ€. μ΄ λ°©μμ μ½λκ° μ΅μ μ΄λΌκ³ νλ¨νλ©΄ 건λλ°κ³ , μΆκ°/λ³κ²½λ λΆλΆ λ± μΆκ°μ μΈ λΆλΆμ λν΄μλ§ λ€μ build λ₯Ό μ§νν©λλ€. μ΄λ‘ μΈν΄ build μλκ° λΉ λ₯΄λ€λ μ₯μ μ΄ μμ§λ§ μ§μνμ§ μλ μμ‘΄μ±μ΄ μμ μλ μλ€λ λ¨μ μ΄ μμ΅λλ€.
Run tests using μ Gradle (Default) μμ IntelliJ IDEA λ‘ λ³κ²½ν λ€ μλλ₯Ό μΈ‘μ ν΄ λ³΄μμ΅λλ€.
λΉλ λ°©μμ λ³κ²½νμ¬, 42μ΄μμ 24μ΄λ‘ λμ λλ μλ λ³νλ₯Ό 체κ°ν μ μμμ΅λλ€.
μ£Όμμ
Build and run using κΉμ§ λ°κΎΈμ§ μμ μ΄μ λ μμ μΈκΈν λ¨μ λλ¬Έμ
λλ€. μΆκ°μ μΈ μ½λ λ³κ²½ λΆλΆμ λν΄μλ§ λΉλλ₯Ό μννκ³ , μ§μνμ§ μλ μμ‘΄μ±μ΄ μμ μ μλ λ¨μ μ΄ μλλ°, μ ν¬ νλ‘μ νΈμμ μ΄ μν©μ λ§μ£Όνμμ΅λλ€.
μ ν¬λ 쿼리 μ±λ₯ μΈ‘μ μ μν΄ ν
μ€νΈμμ dummy λ°μ΄ν°λ₯Ό λ£μ΄μ£Όλ μμ
μ νκ³ μκ³ , μ΄ μμ
μ μν΄ lombok μ μ¬μ©νμμ΅λλ€. κ·Έλ¦¬κ³ test μμλ lombok μ μΈ μ μλλ‘ νκΈ° μν΄ build.gradle νμΌμ λ€μκ³Ό κ°μ΄ μμ‘΄μ±μ μΆκ°νμμ΅λλ€.
testCompileOnly 'org.projectlombok:lombok:1.18.12'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.12'
Groovy
볡μ¬
κ·Έλ¦¬κ³ Build and run using μ IntelliJ IDEA λ‘ μ€μ νκ³ test λ₯Ό λ리면 λ€μκ³Ό κ°μ μλ¬κ° λ°μν©λλ€.
lombok μμ μ§μνλ μ»΄νμΌλ¬κ° μλλΌλ μλ¬ λ΄μ©μ
λλ€. μ΄μ λ°λΌ μμ‘΄μ±μ κ΄ν compile μ κΈ°μ‘΄μ Gradle μ 맑겨λκ³ test λ₯Ό ν λλ§ IntelliJ build λ°©μμ μ΄μ©ν μ μλλ‘ μμ κ°μ΄ μ€μ νμμ΅λλ€.