์ง€๋‚œ 10๋…„ ๋™์•ˆ ์šฐ๋ฆฌ ๋ถ„์•ผ๋Š” ๋ˆˆ๋ถ€์‹  ์„ฑ์žฅ์„ ์ด๋ค˜๋‹ค. 97๋…„๋„๋งŒ ํ•ด๋„ TDD๋ผ๋Š” ๊ฐœ๋…์„ ์•„๋ฌด๋„ ๋ชฐ๋ž๋‹ค. ๊ทธ ๋‹น์‹œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ๋Œ์•„๊ฐ€๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ผํšŒ์„ฑ ์ฝ”๋“œ์— ๋ถˆ๊ณผํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ง€๊ธˆ์€ Agile๊ณผ TDD ๋•ํƒ์— ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž๋™ํ™”ํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋จธ๋“ค์ด ๋งŽ์•„์กŒ๋‹ค. ํ•˜์ง€๋งŒ, ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ๊ธ‰ํ•˜๊ฒŒ ์„œ๋‘๋ฅด๋Š” ์™€์ค‘์— ๋งŽ์€ ํ”„๋กœ๊ทธ๋ž˜๋จธ๋“ค์ด ์ œ๋Œ€๋กœ ๋œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ๋†“์ณ๋ฒ„๋ ธ๋‹ค.

TDD ๋ฒ•์น™ ์„ธ๊ฐ€์ง€

  • ์ฒซ์งธ ๋ฒ•์น™: ์‹คํŒจํ•˜๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๊นŒ์ง€ ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • ๋‘˜์งธ ๋ฒ•์น™: ์ปดํŒŒ์ผ์€ ์‹คํŒจํ•˜์ง€ ์•Š์œผ๋ฉด์„œ ์‹คํ–‰์ด ์‹คํŒจํ•˜๋Š” ์ •๋„๋กœ๋งŒ ๋‹จ์œ„ ํ…Œ์ŠคํŠธํ‹€ ์ž‘์„ฑํ•œ๋‹ค.
  • ์…‹์งธ ๋ฒ•์น™: ํ˜„์žฌ ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ํ†ต๊ณผํ•  ์ •๋„๋กœ๋งŒ ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ํ•˜๊ฒŒ ๋˜๋ฉด, ์ฒ˜์Œ์— ๋กœ์ง์„ ์งค ๋•Œ ํŠน์ • ์ผ€์ด์Šค๋ฅผ ํ†ต๊ณผํ•˜๋Š” ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๋ฅผ ๋จผ์ € ์ž‘์„ฑํ•˜๊ณ  ์ด์— ๋Œ€ํ•œ ๋กœ์ง์„ ์ˆ˜์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ˆ˜๋งŽ์€ ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜๊ฐ€ ๋‚˜์˜จ๋‹ค. ์งง์€ ํ๋ฆ„์œผ๋กœ ์ง„ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ํ•˜๋‚˜์˜ ๊ณผ์ •์€ 30์ดˆ ์ •๋„๋กœ ๋ฌถ์ด๊ฒŒ ๋œ๋‹ค. ๋ฌธ์ œ๋Š”, ์ด ๋ฐฉ์‹์„ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ ๋‚˜์˜ค๋Š” ์–ด๋งˆ๋ฌด์‹œํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์–‘์ด๋‹ค.

๊นจ๋—ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์œ ์ง€ํ•˜๊ธฐ

โ€œ์ง€์ €๋ถ„ํ•ด๋„ ๋นจ๋ฆฌโ€ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์ข‹์„๊นŒ? ํ…Œ์ŠคํŠธ๋งŒ ํ•˜๋ฉด ๊ทธ๋งŒ์ผ๊นŒ? ํ•˜์ง€๋งŒ, ์ด๋Š” ์˜คํžˆ๋ ค ํ…Œ์ŠคํŠธ๋ฅผ ์•ˆํ•˜๋Š”๊ฒŒ ๋‚˜์€ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ์ด ์ด์œ ๋Š” ์‹ค์ œ ์ฝ”๋“œ๊ฐ€ ๋ณ€ํ™”ํ•˜๋ฉด, ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋„ ๋ณ€ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฆ‰, ์‹ค์ œ ์ฝ”๋“œ ๋ณ€ํ™”์— ๋”ฐ๋ผ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋„ ๋ณ€ํ•ด์•ผ ํ•˜๋Š”๋ฐ, โ€œ์ง€์ €๋ถ„ํ•ด๋„ ๋นจ๋ฆฌโ€ ์ž‘์„ฑํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ๋ณ€๊ฒฝ์„ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ ๋‹ค. ์ด๋Š” ๊ณง ๋ถ€๋‹ด์œผ๋กœ ์ด์–ด์ง€๊ณ , ํŒ€์˜ ๋ถˆ๋งŒ์œผ๋กœ ๋ณ€ํ™”ํ•œ๋‹ค. ๊ฒฐ๊ตญ ์ด๋ฅผ ํ์ง€ํ•˜๋Š” ์ƒํ™ฉ์— ์ฒ˜ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ์ด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ์—†์œผ๋ฉด ์ˆ˜์ •ํ•œ ์ฝ”๋“œ๊ฐ€ ์ œ๋Œ€๋กœ ๋„๋Š”์ง€ ํ™•์ธํ•  ๋ฐฉ๋ฒ•์ด ์—†๋‹ค. ๊ฒฐ๊ตญ ์ด๋Š” ๋ง๊ฐ€์ง„ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ ๋‹ค.

ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์‹ค์ œ ์ฝ”๋“œ ๋ชป์ง€ ์•Š๊ฒŒ ์ค‘์š”ํ•˜๋‹ค.

ํ…Œ์ŠคํŠธ๋Š” ์œ ์—ฐ์„ฑ, ์œ ์ง€๋ณด์ˆ˜์„ฑ, ์žฌ์‚ฌ์šฉ์„ฑ์„ ์ œ๊ณตํ•œ๋‹ค

์ฝ”๋“œ์— ์œ ์—ฐ์„ฑ, ์œ ์ง€๋ณด์ˆ˜์„ฑ, ์žฌ์‚ฌ์šฉ์„ฑ์„ ์ œ๊ณตํ•˜๋Š” ๋ฒ„ํŒ€๋ชฉ์ด ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋‹ค. ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ์žˆ์œผ๋ฉด, ๋ณ€๊ฒฝ์ด ๋‘๋ ต์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๊ฐ€ ์—†์œผ๋ฉด ๋ชจ๋“  ๊ฒƒ์€ ์ž ์ •์ ์ธ ๋ฒ„๊ทธ๋‹ค. ์•„ํ‚คํ…์ณ๊ฐ€ ์œ ์—ฐํ•˜๋”๋ผ๋„, ์„ค๊ณ„๋ฅผ ์•„๋ฌด์ง€ ์ž˜ ๋‚˜๋ˆด๋”๋ผ๋„ ์ด๋ฅผ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜๋‹จ์ด ์—†๋‹ค๋ฉด ๊ฐœ๋ฐœ์ž๋Š” ๋ณ€๊ฒฝ์„ ์ฃผ์ €ํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ ๊ฒ€ํ•˜๋Š” ์ž๋™ํ™”๋œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ŠˆํŠธ๋Š” ์„ค๊ณ„์™€ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ตœ๋Œ€ํ•œ ๊นจ๋—ํ•˜๊ฒŒ ๋ณด์กดํ•˜๋Š” ์—ด์‡ ๋‹ค.

๊นจ๋—ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

๊ทธ๋Ÿผ ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ๊นจ๋—ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์„๊นŒ? ๊ฐ€๋…์„ฑ์ด ๊ทธ ํ•ต์‹ฌ์ด๋‹ค.

public void testGetPageHieratchyAsXml() throws Exception {
    crawler.addPage(root, PathParser.parse("PageOne")); // ์ค‘๋ณต(addPage)!
    crawler.addPage(root, PathParser.parse("PageOne.ChildOne")); // ์ค‘๋ณต(addPage)!
    crawler.addPage(root, PathParser.parse("PageTwo")); // ์ค‘๋ณต(addPage)!
 
    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)!
    assertSubString("<name>PageTwo</name>", xml); // ์ค‘๋ณต(assertSubString)!
    assertSubString("<name>ChildOne</name>", xml); // ์ค‘๋ณต(assertSubString)!
}
 
public void testGetPageHieratchyAsXmlDoesntContainSymbolicLinks() throws Exception {
    WikiPage pageOne = crawler.addPage(root, PathParser.parse("PageOne")); // ์ค‘๋ณต(addPage)!
    crawler.addPage(root, PathParser.parse("PageOne.ChildOne")); // ์ค‘๋ณต(addPage)!
    crawler.addPage(root, PathParser.parse("PageTwo")); // ์ค‘๋ณต(addPage)!
 
    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)!
    assertSubString("<name>PageTwo</name>", xml); // ์ค‘๋ณต(assertSubString)!
    assertSubString("<name>ChildOne</name>", xml); // ์ค‘๋ณต(assertSubString)!
    assertNotSubString("SymPage", xml);
}
 
public void testGetDataAsHtml() throws Exception {
  crawler.addPage(root, PathParser.parse("TestPageOne"), "test page"); // ์ค‘๋ณต(addPage)!
 
  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)!
  assertSubString("<Test", xml); // ์ค‘๋ณต(assertSubString)!
}

addPage, assertSubString์˜ ์ค‘๋ณต์ด ๋„ˆ๋ฌด ๋งŽ๋‹ค. ๋˜ ์ž์งˆ๊ตฌ๋ ˆํ•œ ์‚ฌํ•ญ๊นŒ์ง€ ์ž‘์„ฑํ•ด๋‘์–ด ํ‘œํ˜„๋ ฅ์ด ๋–จ์–ด์ง„๋‹ค.

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");
}

์žก๋‹คํ•˜๊ณ  ์„ธ์„ธํ•œ ์ฝ”๋“œ๋ฅผ ๊ฑฐ์˜๋‹ค ์—†์•ด๋‹ค๋Š” ๊ฒƒ์— ์ฃผ๋ชฉํ•˜์ž. ๋ฆฌํŒฉํ† ๋งํ•œ ์ฝ”๋“œ๋Š” Build-operate-check ํŒจํ„ด์„ ์‚ฌ์šฉํ–ˆ๋‹ค.

  • Build: ํ…Œ์ŠคํŠธ ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ ๋‹ค. (given)
  • Operate: ํ…Œ์ŠคํŠธ ์ž๋ฃŒ๋ฅผ ์กฐ์ž‘ํ•œ๋‹ค. (when)
  • Check: ์กฐ์ž‘ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•œ๋‹ค. (then)

๊ด€๋ก€์ ์œผ๋กœ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” given, when, then์„ ์ƒ๊ฐํ•˜๋ฉด ๋˜๊ฒ ๋‹ค.

์ด์ค‘ ํ‘œ์ค€

์ผ๋‹จ ์ด์ค‘ ํ‘œ์ค€์ด ๋ฌด์Šจ ์˜๋„๋กœ ์ ์—ˆ๋Š”์ง€ ๋ถ€ํ„ฐ ์ดํ•ดํ•ด๋ณด์ž. Test Code๋Š” ๋ฌผ๋ก  ์ž˜ ์ž‘์„ฑํ•ด์•ผ ํ•˜์ง€๋งŒ ์‹ค์ œ ์ฝ”๋“œ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•  ํ•„์š”๋Š” ์—†์„ ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, Test code๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํŠน์ง•์„ ๋” ์ž˜ ํ‘œํ˜„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์€ ์ฝ”๋“œ์ผ ์ˆ˜ ์žˆ๋‹ค. ์ด๋Ÿฐ ์˜๋ฏธ์—์„œ ๊ฐ ์ฝ”๋“œ์˜ ์œ„์น˜(์‹ค์ œ, ํ…Œ์ŠคํŠธ)์— ๋”ฐ๋ผ ์ ์šฉ๋˜์–ด์•ผ ํ•˜๋Š” ํ‘œ์ค€์€ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ง์ด๋‹ค.

@Test
public void turnOnLoTempAlarmAtThreashold() throws Exception {
    hw.setTemp(WAY_TOO_COLD); 
    controller.tic(); 
    assertTrue(hw.heaterState());   
    assertTrue(hw.blowerState()); 
    assertFalse(hw.coolerState()); 
    assertFalse(hw.hiTempAlarm());       
    assertTrue(hw.loTempAlarm());
}

์œ„ ์ฝ”๋“œ๋Š” ํ™˜๊ฒฝ ์ œ์–ด ์‹œ์Šคํ…œ์— ์†ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ด๋‹ค. ๋Œ€์ถฉ ๋ณด๋ฉด ์˜จ๋„๊ฐ€ ๋–จ์–ด์ง€๋ฉด ๊ฒฝ๋ณด, ์˜จํ’๊ธฐ, ์†กํ’๊ธฐ๊ฐ€ ๊ฐ€๋™๋˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์ฝ”๋“œ๋ผ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด๋…€์„์„ ์ฝ๊ธฐ๊ฐ€ ์ƒ๋‹นํžˆ ์–ด๋ ต๋‹ค. ๋จผ์ € ์ธ์ˆ˜๋ถ€ํ„ฐ ์ฝ์–ด์•ผ ํ•˜๊ณ , ์•ž์˜ assert๋ฌธ์„ ํ†ตํ•ด true, false๋ฅผ ๋ถ„๊ฐ„ํ•ด์•ผ ํ•œ๋‹ค.

@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
    wayTooCold();
    assertEquals("HBchL", hw.getState()); 
}

๊ทธ๋ž˜์„œ ์œ„์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ–ˆ๋‹ค. uppercase์™€ lowercase๋ฅผ ํ†ตํ•ด On/Off๋ฅผ ๋ถ„๊ฐ„ํ–ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ์•ž์„œ Meaningful Names์˜ โ€œ๊ทธ๋ฆ‡๋œ ์ •๋ณด๋ฅผ ํ”ผํ•˜๋ผโ€ ๊ทœ์น™์— ์œ„๋ฐฐ๋œ๋‹ค. ํ•˜์ง€๋งŒ ์ฝ”๋“œ๋ฅผ ์ฝ์–ด๋ณด์ž. ์•„์ฃผ ์ง๊ด€์ ์œผ๋กœ ํ•ด์„๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

@Test
public void turnOnCoolerAndBlowerIfTooHot() throws Exception {
    tooHot();
    assertEquals("hBChl", hw.getState()); 
}
  
@Test
public void turnOnHeaterAndBlowerIfTooCold() throws Exception {
    tooCold();
    assertEquals("HBchl", hw.getState()); 
}
 
@Test
public void turnOnHiTempAlarmAtThreshold() throws Exception {
    wayTooHot();
    assertEquals("hBCHl", hw.getState()); 
}
 
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
    wayTooCold();
    assertEquals("HBchL", hw.getState()); 
}

๋„ˆ๋ฌด๋‚˜ ์ฝ๊ธฐ ์‰ฝ๋‹ค.

public String getState() {
    String state = "";
    state += heater ? "H" : "h"; 
    state += blower ? "B" : "b"; 
    state += cooler ? "C" : "c"; 
    state += hiTempAlarm ? "H" : "h"; 
    state += loTempAlarm ? "L" : "l"; 
    return state;
}

getState์˜ ๊ฒฝ์šฐ์—๋Š” ๊ต‰์žฅํžˆ ํšจ์œจ์ด ๋‚ฎ๊ฒŒ ์ž‘์„ฑ๋˜์–ด ์žˆ๋‹ค. ํšจ์œจ์„ ๋†’์ด๋ ค๋ฉด StringBuffer๊ฐ€ ๋” ์ ํ•ฉํ•˜๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์œผ๋‚˜ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š๋Š”๋‹ค. ๋ณดํ†ต์˜ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์€ ์ž์›์ด ์ œํ•œ์ ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ๋‚ฎ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด๊ฒƒ์ด ์ด์ค‘ ํ‘œ์ค€์˜ ๋ณธ์งˆ์ด๋‹ค. ์‹ค์ œํ™˜๊ฒฝ์—์„œ๋Š” ์ ˆ๋Œ€๋กœ ์•ˆ๋˜์ง€๋งŒ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ์ „ํ˜€ ๋ฌธ์ œ์—†๋Š” ๋ฐฉ์‹์ด ์žˆ๋‹ค.

ํ…Œ์ŠคํŠธ ๋‹น assert ํ•˜๋‚˜

assert๋ฅผ ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๊ณ  ์ฃผ์žฅํ•˜๋Š” ํ•™ํŒŒ๊ฐ€ ์žˆ๋‹ค. ํ™•์‹คํžˆ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด ์ดํ•ดํ•˜๊ธฐ ๋น ๋ฅด๊ณ  ์‰ฝ๋‹ค.

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>"
  ); 
}

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ํ•„์—ฐ์ ์œผ๋กœ ์ค‘๋ณต๋˜๋Š” ์ฝ”๋“œ๊ฐ€ ๋งŽ์•„์ง„๋‹ค. Template Method ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๋ฉด ์ค‘๋ณต์„ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ๋‹ค. given/when ๋ถ€๋ถ„์„ ๋ถ€๋ชจ ํด๋ž˜์Šค์— ๋‘๊ณ  then ๋ถ€๋ถ„์„ ์ž์‹ ํด๋ž˜์Šค์— ๋‘๋ฉด ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๋ฐฐ๋ณด๋‹ค ๋ฐฐ๊ผฝ์ด ๋”ํฌ๋‹ค. ์˜คํžˆ๋ ค assert๋ฌธ์„ ์–ด๋ ค๋ฒˆ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

ํ…Œ์ŠคํŠธ ๋‹น ๊ฐœ๋… ํ•˜๋‚˜

์œ„์˜ ๋ฐฉ์‹๋ณด๋‹ค ์กฐ๊ธˆ๋” ์ด์น˜์— ๋งž๋Š” ๊ฒƒ์€ ์ด๊ฒƒ์ด๋‹ค. ์žก๋‹คํ•œ ๊ฐœ๋…์„ ์—ฐ์†์œผ๋กœ ํ…Œ์ŠคํŠธํ•˜๋Š” ํ•จ์ˆ˜๋Š” ํ”ผํ•˜์ž.

/**
 * addMonth() ๋ฉ”์„œ๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•˜๋Š” ์žฅํ™ฉํ•œ ์ฝ”๋“œ
 */
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());
}

์œ„๋Š” ์„ธ๊ฐœ์˜ ๊ฐœ๋…์„ ํ…Œ์ŠคํŠธ ํ•˜๊ณ  ์žˆ๋‹ค.

  1. 30์ผ๋กœ ๋๋‚˜๋Š” ๋‹ฌ + 1 month = ์ผ์ด 31์ด๋ฉด ์•ˆ๋œ๋‹ค.
    • 2 months์‹œ ๋‘๋ฒˆ์งธ๊ฐ€ 31์ด๋ฉด ์ตœ์ข… ๊ฒฐ๊ณผ๋Š” 31์ด์–ด์•ผ ํ•œ๋‹ค.
  2. 31์ผ๋กœ ๋๋‚˜๋Š” ๋‹ฌ + 1 month = ์ผ์ด 31์ด๋ฉด ์•ˆ๋œ๋‹ค.

์—ฌ๋Ÿฌ ๊ฐœ๋…์„ ํ…Œ์ŠคํŠธํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๊ฒฝ์šฐ๋Š” ๋ถ„๋ฆฌํ•ด์•ผ ํ•œ๋‹ค.

F.I.R.S.T.

๊นจ๋—ํ•œ ํ…Œ์ŠคํŠธ๊ฐ€ ๋”ฐ๋ฅด๋Š” ๊ทœ์น™์ด๋‹ค.

Fast

ํ…Œ์ŠคํŠธ๋Š” ๋นจ๋ผ์•ผ ํ•œ๋‹ค. ๋น ๋ฅด์ง€ ์•Š์œผ๋ฉด ์ž์ฃผ ๋Œ๋ฆด ์—„๋‘๋ฅผ ๋ชป๋‚ธ๋‹ค. ์ฝ”๋“œ๋ฅผ ๋งˆ์Œ๊ป ์ •๋ฆฌํ•˜๊ธฐ๋„ ์–ด๋ ค์›Œ์ง„๋‹ค.

Independent

๊ฐ ํ…Œ์ŠคํŠธ๋Š” ์„œ๋กœ ์˜์กดํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ๋…๋ฆฝ์ ์œผ๋กœ ์–ด๋– ํ•œ ์ˆœ์„œ๋กœ ์‹คํ–‰ํ•ด๋„ ๊ดœ์ฐฎ์•„์•ผ ํ•œ๋‹ค. ์˜์กด์„ฑ์ด ์žˆ์œผ๋ฉด ๋ฌธ์ œ๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ค์›Œ์ง„๋‹ค.

Repeatable

์–ด๋– ํ•œ ํ™˜๊ฒฝ์—์„œ๋„ ๋ฐ˜๋ณต ๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค. ์‹ค์ œ ํ™˜๊ฒฝ, QA ํ™˜๊ฒฝ, ๋„คํŠธ์›Œํฌ ์—ฐ๊ฒฐ ์œ ๋ฌด ํ™˜๊ฒฝ ๋“ฑ์—์„œ ๋ชจ๋‘ ์‹คํ–‰๊ฐ€๋Šฅํ•ด์•ผ ํ•œ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์˜ ์‹คํŒจ ์›์ธ์„ ๋ณ€๋ช…ํ•  ๊ฑฐ๋ฆฌ๊ฐ€ ์ƒ๊ธด๋‹ค. ๋˜ ํ™˜๊ฒฝ์— ์˜์กด์ ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‹คํ–‰ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๋‹จ์ ๋„ ์ƒ๊ธด๋‹ค.

Self-Validating

ํ…Œ์ŠคํŠธ๋Š” ์„ฑ๊ณต ์•„๋‹ˆ๋ฉด ์‹คํŒจ๋กœ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™€์•ผ ํ•œ๋‹ค. ํ…Œ์ŠคํŠธ๊ฐ€ ์Šค์Šค๋กœ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ๊ฐ€๋Š ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ํŒ๋‹จ์€ ์ฃผ๊ด€์ ์ด ๋˜๊ณ , ์ˆ˜์ž‘์—… ํ‰๊ฐ€๊ฐ€ ํ•„์š”ํ•˜๊ฒŒ ๋œ๋‹ค.

Timely

ํ…Œ์ŠคํŠธ๋Š” ์ ์‹œ์— ์ž‘์„ฑ๋˜์–ด์•ผ ํ•œ๋‹ค. ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋Š” ํ…Œ์ŠคํŠธํ•˜๋ ค๋Š” ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๊ธฐ ์ง์ „์— ๊ตฌํ˜„ํ•œ๋‹ค. ๋งŒ์•ฝ ๋ฐ˜๋Œ€๊ฐ€ ๋˜๋ฉด, ์‹ค์ œ ์ฝ”๋“œ๊ฐ€ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๋„ˆ๋ฌด ์–ด๋ ค์›Œ ๋ณด์ผ ๊ฐ€๋Šฅ์„ฑ์ด ํฌ๋‹ค.

๊ฒฐ๋ก 

  • ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋Š” ์‹ค์ œ ์ฝ”๋“œ ๋งŒํผ์ด๋‚˜ ํ”„๋กœ์ ํŠธ ๊ฑด๊ฐ•์— ์ค‘์š”ํ•˜๋‹ค.
  • ์‹ค์ œ ์ฝ”๋“œ์˜ ์œ ์—ฐ์„ฑ, ์œ ์ง€๋ณด์ˆ˜์„ฑ, ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋ณด์กดํ•˜๊ณ  ๊ฐ•ํ™”ํ•˜๊ธฐ ์œ„ํ•ด ์กด์žฌํ•œ๋‹ค.
  • ์ง€์†์ ์œผ๋กœ ๊นจ๋—ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ฑ…์ž„์ด ์žˆ๋‹ค.

Reference