μ¬λ¬λΆμ ν μ€νΈ μ½λλ₯Ό μμ±νμλμ? μ΄λ² μ₯μμλ ν μ€νΈ μ½λμ μ€μμ±κ³Ό ν μ€νΈ μ½λ μμ±νλ λ²μ κ°λ¨νκ² μκ°νλ €κ³ ν΄μ. μ μμ λ°λ₯΄λ©΄ ν μ€νΈ μ½λμ λν΄μ μ€λͺ νλ €λ©΄ μ± 1κΆμΌλ‘λ λΆμ‘±νλ€κ³ λ§νλ, μμΈν μ€λͺ μ μλκ³ ν΅μ¬λ§ μ€λͺ ν΄μ.
μ λͺ ν TDD λ²μΉ 3κ°μ§κ° μμ΄μ.
μ€ν¨νλ λ¨μ ν μ€νΈλ₯Ό μμ±ν λκΉμ§ μ€μ μ½λλ₯Ό μμ±νμ§ μλλ€.
μ»΄νμΌμ μ€ν¨νμ§ μμΌλ©΄μ μ€νμ΄ μ€ν¨νλ μ λλ‘ λ¨μ ν μ€νΈλ₯Ό μμ±νλ€.
νμ¬ μ€ν¨νλ ν μ€νΈλ₯Ό ν΅κ³Όν μ λλ‘λ§ μ€μ μ½λλ₯Ό μμ±νλ€.
μ΄ κ·μΉλ€μ λ°λ₯΄λ©΄ ν μ€νΈ μ½λμ μ€μ μ½λκ° ν¨κ» λμ€λ μ₯μ μ κ°μ§ μ μμ΄μ. κ·Έλ¦¬κ³ κ°λ°κ³Ό ν μ€νΈκ° λλ΅ 30μ΄ μ£ΌκΈ°λ‘ λ¬Άμ΄κ³ μκ°μ΄ μ§λλ©° μμ² κ°μ λ¬νλ ν μ€νΈ μΌμ΄μ€κ° λμμ.
κ·Έλ°λ° μ΄λ κ² λ°©λν΄μ§λ ν μ€νΈ μ½λλ μ¬κ°ν κ΄λ¦¬ λ¬Έμ λ₯Ό μΌμΌν€κΈ°λ ν΄μ.
ν μ€νΈ μ½λμ μ€μ μ½λμ λμΌν κΈ°μ€μΌλ‘ νμ§μ μ μ§ν νμλ μμ΄μ. νμ§λ§ κΈ°μ€μ΄ μ½κ° λ€λ₯Ό λΏ ν μ€νΈμ½λλ κ³ νμ§μ μ μ§ν΄μΌν΄μ. λνμ μΌλ‘ λ¨μ ν μ€νΈμμλ κ·μΉμ κΉ¨κ³ μμ±ν΄λ λλλ‘ νκ°ν΄μ€μ μ½λκ° λ§κ°μ§λ κ²μ΄μμ. μ§μ λΆν ν μ€νΈ μ½λλ₯Ό μ¬μ©νλ κ²μ ν μ€νΈ μ½λλ₯Ό μ§μ§ μλ κ²λ³΄λ€ μ€νλ € μμ’μ μ μμ΄μ. λμΆ©νλ©΄ μ€νλ € μνλ κ²λ§ λͺ»νκ±°μμ.
ν μ€νΈ μ½λλ μ μ°μ±, μ μ§λ³΄μμ±, μ¬μ¬μ©μ±μ μ 곡ν΄μ. κ·Έλμ ν μ€νΈ μ½λλ₯Ό κΉ¨λνκ² μ μ§νμ§ μμΌλ©΄ μ΄λ° μ₯μ λ μμ΄λ²λ €μ. ν μ€νΈ μ½λκ° μκΈ° λλ¬Έμ μ½λ λ³κ²½μ λ μμ λ‘κ² ν μ μμλ μ₯μ μ΄ μλλ°, ν μ€νΈ μ½λλ₯Ό κΉ¨λνκ² μ μ§νμ§ λͺ»νλ©΄ μ₯μ μ΄ μ¬λΌμ§κ³ μ€μ μ½λλ ν¨κ» λ§κ°μ§λ κ±°μμ.
ν μ€νΈ μ½λλ₯Ό λ§λ€κΈ°μν΄ νμν κ²μ λ± 1κ°μ§ "κ°λ μ±"μ΄μμ. μ΄λ μ½λλ€κ³Ό κ°μ΄ λͺ λ£μ±, λ¨μμ±, νλΆν ννλ ₯μ΄ νμν΄μ. μ΅μμ ννμΌλ‘ λ§μ κ²μ λνλ΄μΌν΄μ. λ€μ ν μ€νΈ μΌμ΄μ€λ₯Ό 보면μ μ΄μΌκΈ° ν΄μ.
public void testGetPageHieratchyAsXml() throws Exception { crawler.addPage(root, PathParser.parse("PageOne")); crawler.addPage(root, PathParser.parse("PageOne.ChildOne")); crawler.addPage(root, PathParser.parse("PageTwo")); request.setResource("root"); request.addInput("type", "pages"); Responder responder = new SerializedPageResponder(); SimpleResponse response = (SimpleResponse) responder.makeResponse(new FitNesseContext(root), request); String xml = response.getContent(); assertEquals("text/xml", response.getContentType()); assertSubString("<name>PageOne</name>", xml); assertSubString("<name>PageTwo</name>", xml); assertSubString("<name>ChildOne</name>", xml);}public void testGetPageHieratchyAsXmlDoesntContainSymbolicLinks() throws Exception { WikiPage pageOne = crawler.addPage(root, PathParser.parse("PageOne")); crawler.addPage(root, PathParser.parse("PageOne.ChildOne")); crawler.addPage(root, PathParser.parse("PageTwo")); PageData data = pageOne.getData(); WikiPageProperties properties = data.getProperties(); WikiPageProperty symLinks = properties.set(SymbolicPage.PROPERTY_NAME); symLinks.set("SymPage", "PageTwo"); pageOne.commit(data); request.setResource("root"); request.addInput("type", "pages"); Responder responder = new SerializedPageResponder(); SimpleResponse response = (SimpleResponse) responder.makeResponse(new FitNesseContext(root), request); String xml = response.getContent(); assertEquals("text/xml", response.getContentType()); assertSubString("<name>PageOne</name>", xml); assertSubString("<name>PageTwo</name>", xml); assertSubString("<name>ChildOne</name>", xml); assertNotSubString("SymPage", xml);}public void testGetDataAsHtml() throws Exception { crawler.addPage(root, PathParser.parse("TestPageOne"), "test page"); request.setResource("TestPageOne"); request.addInput("type", "data"); Responder responder = new SerializedPageResponder(); SimpleResponse response = (SimpleResponse) responder.makeResponse(new FitNesseContext(root), request); String xml = response.getContent(); assertEquals("text/xml", response.getContentType()); assertSubString("test page", xml); assertSubString("<Test", xml);}μ ν μ€νΈ μ½λλ μμ§κ΅¬λ ν μ¬νμ λ무 λ§μ΄ νννκ³ μ€λ³΅λλ μ½λλ λ§μμ ννλ ₯μ΄ λ¨μ΄μ Έμ. μλλ₯Ό λ°λ‘ μμλ³Ό μ μκ³ , μκ΄μλ crawlerκ°μ κ°μ²΄λ₯Ό μμμΌ νλ€κ±°λ, λ°μ΄ν° νλ¦λ λ§μ΄ νμ ν΄μΌν΄μ. κ·Έλμ μ‘μμ΄ λ§μμ. μ΄λ₯Ό μ’λ κΉ¨λνκ² κ°μ ν΄λ΄μ.
public void testGetPageHierarchyAsXml() throws Exception { makePages("PageOne", "PageOne.ChildOne", "PageTwo"); submitRequest("root", "type:pages"); assertResponseIsXML(); assertResponseContains("<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");}public void testSymbolicLinksAreNotInXmlPageHierarchy() throws Exception { WikiPage page = makePage("PageOne"); makePages("PageOne.ChildOne", "PageTwo"); addLinkTo(page, "PageTwo", "SymPage"); submitRequest("root", "type:pages"); assertResponseIsXML(); assertResponseContains("<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"); assertResponseDoesNotContain("SymPage");}public void testGetDataAsXml() throws Exception { makePageWithContent("TestPageOne", "test page"); submitRequest("TestPageOne", "type:data"); assertResponseIsXML(); assertResponseContains("test page", "<Test");}μ΄μ λ°λ μ½λλ₯Ό 보면 μ΄λ μ κ°μ? testGetPageHAierarchyAsXml ν μ€νΈ μ½λλ₯Ό 보면, [makePages(νμ΄μ§ μμ±) -> submitRequest(μμ² λ³΄λ΄κΈ°) -> μλ΅ κ²μ¦]μ νλ¦μΌλ‘ ν μ€νΈλ₯Ό νκ³ μμμ μ μ μμ΄μ. ν μ€νΈμ νμν μλ£ μ νκ³Ό ν¨μλ§ μ¬μ©νκ³ λλ¨Έμ§κ° λ€ λΉ μ Έμ μ΄μν μ‘μμμ΄ ν μ€νΈ μ½λκ° μ μ½νμ.
κ·Έλ¦¬κ³ μμ κ°μ΄ 3λ¨κ³λ‘ μ΄λ£¨μ΄μ§ ꡬ쑰λ λ³΄ν΅ BUILD-OPERATE-CHECK ν¨ν΄μ νμ©νμ¬ κ°λ μ±μ λλ €μ. κ·Έλ¦¬κ³ μ΄ ν¨ν΄μ νμ©ν λλ given-when-thenμ κ΄λ‘μ μΌλ‘ μ£ΌμμΌλ‘ μ΄μ©νκ±°λ ν¨μλͺ μ μΆκ°ν΄μ ꡬλΆν΄μ€μ.
BUILD(given) : ν μ€νΈ μλ£ μμ±
OPERATE(when) : ν μ€νΈ μλ£ μ‘°μ
CHECK(then) : μ‘°μ κ²°κ³Ό νμΈ
κ·Έλ¦¬κ³ μ κ°μ λ μ½λλ₯Ό 보면 μ§μ μ‘°μνλ APIλ₯Ό μ¬μ©νμ§ μκ³ , API μμ ν¨μμ μ νΈλ¦¬ν°λ₯Ό ꡬννμ¬ μ¬μ©νκ³ μμ΄μ. μ΄λ κ² ν μ€νΈλ₯Ό μν λλ©μΈ νΉν μΈμ΄(DSL)λ₯Ό νμ©νλ©΄ ν μ€νΈλ₯Ό μ½μ λ μκ° μ΄ν΄νκΈ° μ¬μμ Έμ. μ΄λ° μΈμ΄λ₯Ό ν μ€νΈ μΈμ΄λΌκ³ ν΄μ. κ·Έλμ μ κ°μ μ²λΌ μλ ¨λ κ°λ°μλ μ’λ κ°κ²°νκ³ ννλ ₯μ΄ νλΆν μ½λλ‘ λ¦¬ν©ν°λ§ ν΄μΌν΄μ. κ·Έλ°λ° μ¬μ€ μ΄λ° μ©μ΄λ€μ λͺ°λΌλ 곡λΆνκ±°μ λ°λΌμ νλ€λ³΄λ©΄ μμκ°μ μ½λκ° λμ¬κ±°λΌκ³ μκ°ν΄μ.
μ€μ μ½λλ ν μ€νΈ μ½λλ νμ§μ΄ μ’μμΌν΄μ. νμ§λ§ κ·Έλ λ€κ³ κ·Έ κΈ°μ€μ΄λ νμ€μ΄ κ°μ νμλ μμ΄μ. μλ₯Ό λ€λ©΄ μ€μ μ½λμ ν μ€νΈ μ½λλ μ€μ μ€νλλ νκ²½μ΄ λ€λ₯Ό νλ₯ μ΄ λ§€μ° λκΈ° λλ¬Έμ λ무 μ±λ₯μ μ€μμνκ³ μ§€ νμλ μμ΄μ. μλ₯Ό λ€λ©΄ κ΅³μ΄ μ΄λ€ ν μ€νΈλ₯Ό μν λ¬Έμμ΄μ λ§λ€λ String μλ£ν λμ StringBufferλ₯Ό μΈ νμκ° μμ΄μ. μ°λ¦¬λ λ¬Έμμ΄ μ°μ°μ μ¬λ¬λ² ν΄μΌν λ StringBufferλ₯Ό μ΄μ©νλ©΄ ν¨μ¬ λ©λͺ¨λ¦¬ ν¨μ¨μ μ΄λΌλ κ²μ μμ§λ§, ν μ€νΈ νκ²½μ μ€μ νκ²½κ³Ό λ¬λ¦¬ μμμ΄ μ νλ νλ₯ μ΄ μ κΈ° λλ¬Έμ κ³ λ €ν νμκ° μμ΄μ. λ¬Όλ‘ λ§μ½ λ¬΄λ¦¬κ° λλ μ½λλΌλ©΄ λ°κΏμ£Όλκ² μ’κ² μ£ . μ΄μ κ°μ μ΄μ λλ¬Έμ ν μ€νΈ μ½λμ μ€μ μ½λλ λ©λͺ¨λ¦¬λ CPU ν¨μ¨μ κ΄λ ¨ μλ κ²½μ°μ νμ§μ 무μν΄λ λκ³ , μλ‘ λ€λ₯Έ νμ€μ κ°μ§ μ μμ΄μ.
assertλ₯Ό λ°λμ 1κ°λ§ μ¬μ©ν΄μΌ νλ€κ³ μ£Όμ₯νκΈ°λ ν΄μ. κ·Έλ°λ° μ€μ λ‘ κ²°λ‘ μ΄ 1κ°μΈ ν¨μλ μ½λλ₯Ό μ΄ν΄νκΈ° μ½κ³ λΉ¨λΌμ. μ μ½λμ€ μΌλΆλ₯Ό μμλ‘ λ€μ κ°μ Έμ λ³Όκ²μ.
public void testGetPageHierarchyAsXml() throws Exception { makePages("PageOne", "PageOne.ChildOne", "PageTwo"); submitRequest("root", "type:pages"); assertResponseIsXML(); assertResponseContains("<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");}μ μ½λλ "μΆλ ₯μ΄ XMLμ΄λ€.", "νΉμ λ¬Έμμ΄μ ν¬ν¨νλ€."λ assertλ¬Έ 2κ°λ₯Ό κ°μ§κ³ μμ΄μ. κ·Έλ¬λ©΄ μ΄λ₯Ό μͺΌκ°€ μ μμ΄μ.
public void testGetPageHierarchyAsXml() throws Exception { givenPages("PageOne", "PageOne.ChildOne", "PageTwo"); whenRequestIsIssued("root", "type:pages"); thenResponseShouldBeXML();}public void testGetPageHierarchyHasRightTags() throws Exception { givenPages("PageOne", "PageOne.ChildOne", "PageTwo"); whenRequestIsIssued("root", "type:pages"); thenResponseShouldContain( "<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>" );}μ΄λ κ² μͺΌκ°λ©΄ κ° ν μ€νΈλ§λ€ νμΈνλ κ²μ΄ ν¨μ¬ λͺ νν΄μ Έμ. κ·Έλ¦¬κ³ μΆκ°λ‘ given-when-then κ΄λ‘λ₯Ό μ¬μ©ν΄μ μμ ν ꡬλΆνμ¬ κ°λ μ±μ λμμ΄μ. λ¬Όλ‘ μ μ½λμ μ€λ³΅λλ μ½λκ° λ§μμ§λ λ¨μ μ΄ μκ²Όμ΄μ. μ΄λ° μ€λ³΅μ template method ν¨ν΄μ μ¬μ©νλ©΄ μ κ±°ν μ μμ΄μ. given, when λΆλΆμ λΆλͺ¨ ν΄λμ€μ λκ³ then λΆλΆμ μμ ν΄λμ€μ λλ©΄λμ. μλλ©΄ ν μ€νΈ ν΄λμ€λ₯Ό λ§λ€μ΄ @before, @test λΆλΆμ λλ μ λ§λ€μ΄λ μ’μμ. κ·Έλ°λ° μ¬μ€ μ΄κ²μ κ² λ°μ Έλ³΄λ©΄ κ·Έλ₯ μ€λ³΅μΌλ‘ νλκ² ν¨μ¬ μ’λ€κ³ μ μκ° μκ°νλ°μ.
ν μ€νΈ λΉ assert νλ λΌλ κ·μΉλ μ’μ§λ§ ν μ€νΈ λΉ κ°λ νλλΌλ κ·μΉμΌλ‘ μκ°νλ κ²μ΄ λ μ’μ μλ μμ κ² κ°μμ. λ€μ μμ μ½λλ₯Ό λ΄μ.
public void testAddMonths() { SerialDate d1 = SerialDate.createInstance(31, 5, 2004); SerialDate d2 = SerialDate.addMonths(1, d1); assertEquals(30, d2.getDayOfMonth()); assertEquals(6, d2.getMonth()); assertEquals(2004, d2.getYYYY()); SerialDate d3 = SerialDate.addMonths(2, d1); assertEquals(31, d3.getDayOfMonth()); assertEquals(7, d3.getMonth()); assertEquals(2004, d3.getYYYY()); SerialDate d4 = SerialDate.addMonths(1, SerialDate.addMonths(1, d1)); assertEquals(30, d4.getDayOfMonth()); assertEquals(7, d4.getMonth()); assertEquals(2004, d4.getYYYY());}μ μ½λλ₯Ό 보면 μ°μλ ν μ€νΈλ₯Ό νλμ ν μ€νΈ ν¨μλ‘ νκ³ μμ΄μ. μ ν μ€νΈλ μ€νλ € κ° μ μλν΄ μ΄ν΄ν΄μΌνκΈ° λλ¬Έμ μ’μ§ μλ€κ³ μκ°ν΄μ. κ·Έλμ μ ν μ€νΈλ₯Ό 3κ°λ‘ μͺΌκ°λ΄μ.
30μΌλ‘ λλλ ν λ¬μ λνλ©΄ λ μ§λ 30μΌμ΄ λμ΄μΌμ§ 31μΌμ΄ λμ΄μλ μ λλ€.
λ λ¬μ λνλ©΄ κ·Έλ¦¬κ³ λ λ²μ§Έ λ¬μ΄ 31μΌλ‘ λλλ©΄ λ μ§λ 31μΌμ΄ λμ΄μΌ νλ€.
31μΌλ‘ λλλ ν λ¬μ λνλ©΄ λ μ§λ 30μΌμ΄ λμ΄μΌμ§ 31μΌμ΄ λλ©΄ μλλ€.
μ΄λ κ² λ³΄λ©΄ ν¨μ¬ ν μ€νΈ μΌμ΄μ€κ° λͺ ννκ² κ΅¬λΆλμκ³ , 28μΌμ λν΄μλ κ³ λ €ν κ²μΈμ§λ μ½κ² μκ°λμ μ±μμ€ κ² κ°μμ.
κ·Έλμ κ²°λ‘ μ "κ°λ λΉ assert λ¬Έ μλ₯Ό μ΅μλ‘ μ€μ¬λΌ"μ "ν μ€νΈ ν¨μ νλλ κ°λ νλλ§ ν μ€νΈνλΌ"μμ.
κΉ¨λν ν μ€νΈλ λ€μ― κ°μ§ κ·μΉμ λ°λΌμ. κ·Έλ¦¬κ³ κ° κ·μΉμμ 첫 κΈμλ₯Ό λ°λ©΄ FIRSTκ° λμ.
Fast(λΉ λ₯΄κ²): ν μ€νΈλ 빨리 λμμΌν΄μ. κ·ΈλμΌ ν μ€νΈλ₯Ό μμ£Ό λ릴 μλκ° λκ³ λ¬Έμ λ₯Ό μ°Ύμλ΄ κ³ μΉ μ μμ΄μ.
Independent(λ 립μ μΌλ‘): κ° ν μ€νΈλ μλ‘ μμ‘΄νλ©΄ μλΌμ. ν ν μ€νΈκ° λ€μ ν μ€νΈκ° μ€νλ νκ²½μ μ€λΉνλ©΄ μλΌμ. ν μ€νΈκ° μμ‘΄μ΄ μκΈ°κ³ μμκ° μκΈ°λ©΄ νλκ° μ€ν¨ν λ μ°λ¬μ μ€ν¨νκ³ κ²°ν¨ μ°ΎκΈ°κ° νλ€μ΄μ§ κ±°μμ.
Repeatable(λ°λ³΅κ°λ₯νκ²): ν μ€νΈλ μ΄λ€ νκ²½μμλ λ°λ³΅ κ°λ₯ν΄μΌν΄μ. μ€μ νκ²½, QA νκ²½, λ€νΈμν¬ μλλ νκ²½μμλ μ€νν μ μμ΄μΌ ν΄μ. (κ°μΈμ μΈ μ¬κ²¬μΌλ‘ μΈν°λ·μ΄ μλλκ² μ€νλ € μ΄μν νμλμμ λͺ¨λ νκ²½μ κ³ λ €ν΄μ€μΌν κΉ?? λΌλ μꡬμ¬μ΄ λ λ€.)
Self-Validating(μκ°κ²μ¦νλ): ν μ€νΈλ boolκ°μΌλ‘ κ²°κ³Όλ₯Ό λ΄μΌν΄μ. μ±κ³΅ or μ€ν¨ λμ€ νλκ° λ°λμ λμμΌν΄μ. μ±κ³΅/μ€ν¨λ₯Ό νλ¨νκΈ° μν΄ μ΄λ€ λ‘κ·Έλ₯Ό μ½κ² λ§λ λ€κ±°λ νλ λ°©μμ μλμ. μμμ νλ¨λμ΄ κ°κ΄μ μΈ κ²°κ³Όλ₯Ό λμΆν΄μ€μΌ ν΄μ.
Timely(μ μμ): ν μ€νΈλ μ μμ μμ±ν΄μΌν΄μ. λ¨μ ν μ€νΈλ ν μ€νΈνλ €λ μ€μ μ½λλ₯Ό ꡬννκΈ° μ§μ μ ꡬνν΄μ. μ€μ μ½λλ₯Ό ꡬννκ³ ν μ€νΈ μ½λλ₯Ό λ§λ€λ©΄ μ€μ μ½λκ° ν μ€νΈνκΈ° μ΄λ €μμ§κ±°μμ. ν μ€νΈνκΈ° λΆκ°λ₯νλλ‘ μ€μ μ½λκ° μ€κ³λ μ§λ λͺ°λΌμ.
μ΄λ² μ₯μμλ μλ° κ²ν₯κΈ° μ λλ‘λ§ κΉ¨λν ν μ€νΈ μ½λμ λν΄ μκ°νμ΄μ. ν μ€νΈ μ½λλ μ€μ μ½λλ³΄λ€ μ€μν μλ μμ΄μ. μ€μ μ½λμ μ μ°μ±, μ μ§λ³΄μμ±, μ¬μ¬μ©μ±μ 보쑴νκ³ κ°νμμΌμ£ΌκΈ° λλ¬Έμ΄μμ. κ·Έλ κΈ° λλ¬Έμ ν μ€νΈ μ½λλ₯Ό μ§μμ μΌλ‘ κ΄λ¦¬νκ³ ννλ ₯μ λμ΄κ³ κ°κ²°νκ² μ 리ν΄μΌν΄μ. κ·Έλ¦¬κ³ ν μ€νΈ APIλ₯Ό ꡬνν΄ λλ©μΈ νΉν μΈμ΄(DSL)μ λ§λ€μ΄μΌν΄μ. κ·Έλ¬λ©΄ ν μ€νΈ μ½λλ₯Ό μμ±νκΈ°λ μ¬μμ§ κ±°μμ.
ν μ€νΈ μ½λκ° λ§κ°μ§λ©΄ μ€μ μ½λλ λ§κ°μ§κ²λμ. ν μ€νΈ μ½λλ₯Ό κΉ¨λνκ² μ μ§ν©μλ€!!
κ·Έλ°λ° κ³μ 1μ₯λΆν° μ μ¬λΌμ€λ©΄μ λλ κ²μ΄ μμ§ μλμ?? λͺ¨λ κ²μ λΆλ¦¬μ κ°λ μ±μ΄ μ€μ¬μ΄λΌκ³ νμ λκ»΄μ. κ·Έλ¦¬κ³ λΆλ¦¬μ κ°λ μ±μ μ μ λ‘ ν μ λμ λ§μΈλκ° μλ κ°λ°μλΌλ©΄ μ’λ κ·Έ λ§μΈλλ₯Ό ꡬ체νν μ μλ μ± μ΄λΌκ³ λκ»΄μ§λ€μ.