본문으둜 κ±΄λ„ˆλ›°κΈ°

🌈 Chapter 3: 도ꡬ μ†Œκ°œ

πŸ“š κ°„λž΅ν•œ JUnit 4 μ†Œκ°œβ€‹

  • μžλ°” ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬λ‘œ JUnit 4λ₯Ό μ‚¬μš©ν•œλ‹€.
  • JUnit은 λ¦¬ν”Œλ ‰μ…˜μ„ 톡해 클래슀 ꡬ쑰λ₯Ό νŒŒμ•…ν•œ ν›„ ν•΄λ‹Ή 클래슀 λ‚΄μ—μ„œ ν…ŒμŠ€νŠΈλ₯Ό λ‚˜νƒ€λ‚΄λŠ” 것을 λͺ¨λ‘ μ‹€ν–‰ν•œλ‹€.
  • 이λ₯Ό ν…Œλ©΄, λ‹€μŒμ€ Entry 객체의 μ»¬λ ‰μ…˜μ„ κ΄€λ¦¬ν•˜λŠ” Catalog 클래슀λ₯Ό μ‹œν—˜ν•˜λŠ” ν…ŒμŠ€νŠΈλ‹€.
public class CatalogTest {
private final Catalog catalog = new Catalog();

@Test public void containsAnAddedEntry() {
Entry entry = new Entry("fish", "chips");
catalog.add(entry);
assertTrue(catalog.contains(entry));
}

@Test public void indexesEntriesByName() {
Entry entry = new Entry("fish", "chips");
catalog.add(entry);
assertEquals(entry, catalog.entryFor("fish"));
assertNull(catalog.entryFor("missing name"));
}
}

🎈 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€β€‹

  • JUnitμ—μ„œλŠ” @TestλΌλŠ” μ• λ…Έν…Œμ΄μ…˜μ΄ μ§€μ •λœ λ©”μ„œλ“œλŠ” λͺ¨λ‘ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ‘œ μ·¨κΈ‰ν•œλ‹€. ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλŠ” 값을 λ°˜ν™˜ν•˜κ±°λ‚˜ λ§€κ°œλ³€μˆ˜λ₯Ό λ°›μ•„μ„œλŠ” μ•ˆ λœλ‹€. μœ„ 예제의 경우 CatalogTest ν΄λž˜μŠ€μ—λŠ” containsAnAddedEntry, indexesEntriesByNameμ΄λΌλŠ” ν…ŒμŠ€νŠΈ 두 κ°œκ°€ μ •μ˜λΌ μžˆλ‹€.
  • JUnitμ—μ„œλŠ” ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•˜κΈ° μœ„ν•΄ ν…ŒμŠ€νŠΈ 클래슀의 μƒˆ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•œ ν›„ μ μ ˆν•œ ν…ŒμŠ€νŠΈ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€.

🎈 단정​

  • JUnit ν…ŒμŠ€νŠΈμ—μ„œλŠ” ν…ŒμŠ€νŠΈ λŒ€μƒ 객체λ₯Ό ν˜ΈμΆœν•˜κ³  κ·Έ κ²°κ³Όλ₯Ό 단정(assertion)ν•˜λŠ”λ°, 보톡 JUnit에 μ •μ˜λΌ μžˆλŠ” 단정 λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λ©°, μ΄λŸ¬ν•œ λ©”μ„œλ“œλŠ” 단정이 μ‹€νŒ¨ν• λ•Œ μœ μš©ν•œ 였λ₯˜ λ©”μ‹œμ§€λ₯Ό λ§Œλ“€μ–΄λ‚Έλ‹€.
  • 예λ₯Ό λ“€μ–΄, CatalogTestμ—μ„œλŠ” JUnit의 μ„Έ 가지 단정 λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν–ˆλŠ”λ°, assertTrueλŠ” ν‘œν˜„μ‹μ΄ μ°Έμž„μ„ λ‹¨μ •ν•˜κ³  assertNull은 객체 μ°Έμ‘°κ°€ nullμž„μ„ λ‹¨μ •ν•˜λ©°, assertEqualsλŠ” 두 값이 동일함을 λ‹¨μ •ν•œλ‹€. λ§Œμ•½ μ‹€νŒ¨ν•˜λ©΄ μ˜ˆμƒ κ°’κ³Ό μ‹€μ œ 값을 λΉ„κ΅ν•œ λ‚΄μš©μ„ 보여쀀닀.

🎈 μ˜ˆμ™Έ μ˜ˆμƒν•˜κΈ°β€‹

  • @Test μ• λ…Έν…Œμ΄μ…˜μ€ 선택적인 λ§€κ°œλ³€μˆ˜λ‘œ expectedλΌλŠ” 것을 μ§€μ›ν•œλ‹€. 이 λ§€κ°œλ³€μˆ˜λŠ” ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€μ—μ„œ μ˜ˆμ™Έλ₯Ό 던질 κ²ƒμœΌλ‘œ μ„ μ–Έν•œλ‹€. ν…ŒμŠ€νŠΈμ—μ„œ μ˜ˆμ™Έλ₯Ό λ˜μ§€μ§€ μ•Šκ±°λ‚˜ λ‹€λ₯Έ μœ ν˜•μ˜ μ˜ˆμ™Έλ₯Ό λ˜μ§€λ©΄ ν…ŒμŠ€νŠΈκ°€ μ‹€νŒ¨ν•œλ‹€.
  • 예λ₯Ό λ“€μ–΄, λ‹€μŒ ν…ŒμŠ€νŠΈμ—μ„œλŠ” Catalogμ—μ„œ 두 ν•­λͺ©μ΄ 같은 μ΄λ¦„μœΌλ‘œ 좔가될 경우 IllegalArgumentException을 λ˜μ§€λŠ”μ§€ κ²€μ‚¬ν•œλ‹€.
@Test(expected=IllegalArgumentException)
public void cannotAddTwoEntriesWithTheSameName() {
catalog.add(new Entry("fish", "chips"));
catalog.add(new Entry("fish", "peas"));
}

🎈 ν…ŒμŠ€νŠΈ ν”½μŠ€μ²˜β€‹

  • ν…ŒμŠ€νŠΈ ν”½μŠ€μ²˜(test fixture)λŠ” ν…ŒμŠ€νŠΈλ₯Ό μ‹œμž‘ν•  λ•Œ μ‘΄μž¬ν•˜λŠ” κ³ μ •λœ μƒνƒœλ₯Ό μ˜λ―Έν•œλ‹€. ν…ŒμŠ€νŠΈ ν”½μŠ€μ²˜λŠ” ν…ŒμŠ€νŠΈκ°€ 반볡 κ°€λŠ₯함을 보μž₯ν•œλ‹€. 즉, ν…ŒμŠ€νŠΈκ°€ 싀행될 λ•Œλ§ˆλ‹€ ν•΄λ‹Ή ν…ŒμŠ€νŠΈλŠ” λ™μΌν•œ μƒνƒœλ‘œ μ‹œμž‘ν•˜λ―€λ‘œ λ™μΌν•œ κ²°κ³Όλ₯Ό λ‚Ό 것이닀.
  • ν”½μŠ€μ²˜λŠ” ν…ŒμŠ€νŠΈκ°€ μ‹€ν–‰λ˜κΈ° 전에 μ€€λΉ„ν•΄μ„œ ν…ŒμŠ€νŠΈ 싀행이 μ™„λ£Œλœ 후에 정리할 수 μžˆλ‹€.
  • JUnit ν…ŒμŠ€νŠΈμ—μ„œ μ‚¬μš©λ˜λŠ” ν”½μŠ€μ²˜λŠ” ν•΄λ‹Ή ν…ŒμŠ€νŠΈλ₯Ό μ •μ˜ν•œ ν΄λž˜μŠ€μ—μ„œ κ΄€λ¦¬ν•˜κ³  객체의 ν•„λ“œμ— μ €μž₯λœλ‹€. 같은 ν΄λž˜μŠ€μ— μ •μ˜λœ ν…ŒμŠ€νŠΈλŠ” λͺ¨λ‘ λ™μΌν•œ ν”½μŠ€μ²˜λ₯Ό 가지고 μ‹œμž‘ν•˜λ©°, 싀행될 λ•Œ ν•΄λ‹Ή ν”½μŠ€μ²˜λ₯Ό 변경해도 λœλ‹€. CatalogTestμ—μ„œ ν”½μŠ€μ²˜λŠ” catalog ν•„λ“œμ— μ €μž₯된 빈 Catalog 객체닀.
  • JUnitμ—μ„œλŠ” μ• λ…Έν…Œμ΄μ…˜μœΌλ‘œ ν”½μŠ€μ²˜λ₯Ό μ€€λΉ„ν•˜κ±°λ‚˜ μ •λ¦¬ν•˜λŠ” λ©”μ„œλ“œλ₯Ό ꡬ뢄할 μˆ˜λ„ μžˆλ‹€. ν…ŒμŠ€νŠΈλ₯Ό μ‹€ν–‰ν•˜κΈ° 전에 @Before μ• λ…Έν…Œμ΄μ…˜μ΄ μ§€μ •λœ λ©”μ„œλ“œλ₯Ό λͺ¨λ‘ μ‹€ν–‰ν•˜κ³ , ν”½μŠ€μ²˜λ₯Ό μ •λ¦¬ν•˜κΈ° μœ„ν•΄ ν…ŒμŠ€νŠΈκ°€ μ‹€ν–‰λœ ν›„ @AfterλΌλŠ” μ• λ…Έν…Œμ΄μ…˜μ΄ μ§€μ •λœ λ©”μ„œλ“œλ₯Ό μ‹€ν–‰ν•œλ‹€.
  • λ§Žμ€ JUnit ν…ŒμŠ€νŠΈμ—μ„œ λͺ…μ‹œμ μœΌλ‘œ ν”½μŠ€μ²˜λ₯Ό μ •λ¦¬ν•˜μ§€ μ•Šμ•„λ„ λ˜λŠ”λ°, ν”½μŠ€μ²˜λ₯Ό μ€€λΉ„ν•  λ•Œ JVM 가비지 μ»¬λ ‰μ…˜μœΌλ‘œ μƒμ„±λœ 객체λ₯Ό μˆ˜κ±°ν•˜λŠ” κ²ƒλ§ŒμœΌλ‘œλ„ μΆ©λΆ„ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.
  • λ‹€μŒ 예처럼 ν…ŒμŠ€νŠΈμ—μ„œ λͺ¨λ‘ λ™μΌν•œ ν•­λͺ©μœΌλ‘œ catalogλ₯Ό μ΄ˆκΈ°ν™”ν•  수 μžˆλ‹€.
public class CatalogTest {
final Catalog catalog = new Catalog();
final Entry entry = new Entry("fish", "chips");

@Before public void fillTheCatalog() {
catalog.add(entry);
}

// ...
}

🎈 ν…ŒμŠ€νŠΈ λŸ¬λ„ˆβ€‹

  • JUnit이 클래슀λ₯Ό λŒ€μƒμœΌλ‘œ λ¦¬ν”Œλ ‰μ…˜μ„ μˆ˜ν–‰ν•΄ ν…ŒμŠ€νŠΈλ₯Ό μ°Ύμ•„ ν•΄λ‹Ή ν…ŒμŠ€νŠΈλ₯Ό μ‹«ν–‰ν•˜λŠ” 방식은 ν…ŒμŠ€νŠΈ λŸ¬λ„ˆμ—μ„œ μ œμ–΄ν•œλ‹€. ν΄λž˜μŠ€μ— μ‚¬μš©λ˜λŠ” λŸ¬λ„ˆλŠ” @RunWith μ• λ…Έν…Œμ΄μ…˜μœΌλ‘œ μ„€μ •ν•  수 μžˆλ‹€.

πŸ“š ν–„ν¬λ ˆμŠ€νŠΈ λ§€μ²˜μ™€ assertThat()​

  • ν–„ν¬λ ˆμŠ€νŠΈλŠ” 맀칭 쑰건을 μ„ μ–Έμ μœΌλ‘œ μž‘μ„±ν•˜λŠ” ν”„λ ˆμž„μ›Œν¬λ‹€. ν–„ν¬λ ˆμŠ€νŠΈ μžμ²΄λŠ” ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬κ°€ μ•„λ‹ˆμ§€λ§Œ JUnitμ΄λ‚˜ jMock을 λΉ„λ‘―ν•΄ μœˆλ„λ¦¬μ»€ 같은 μ—¬λŸ¬ ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬μ—μ„œ 쓰인닀.
  • ν–„ν¬λ ˆμŠ€νŠΈμ˜ λ§€μ²˜λŠ” νŠΉμ • 객체가 μ–΄λ–€ 쑰건과 μΌμΉ˜ν•˜λŠ”μ§€ μ•Œλ €μ£Όλ©°, ν•΄λ‹Ή μ‘°κ±΄μ΄λ‚˜ 객체가 μ–΄λ–€ 쑰건과 μΌμΉ˜ν•˜μ§€ μ•ŠλŠ” μ΄μœ μ„ κΈ°μˆ ν•  수 μžˆλ‹€.
  • 이λ₯Όν…Œλ©΄, λ‹€μŒ μ½”λ“œλŠ” νŠΉμ • λΆ€λ¬Έμžμ—΄(substring)을 담은 λ¬Έμžμ—΄κ³Ό λ§€μΉ­λ˜λŠ” 맀처λ₯Ό μƒμ„±ν•˜κ³  ν•΄λ‹Ή 맀처λ₯Ό μ‚¬μš©ν•΄ 단정을 μˆ˜ν–‰ν•œλ‹€.
String s = "yes we have no bananas today";

Matcher<String> containsBananas = new StringContains("bananas");
Matcher<String> containsMangoes = new StringContains("mangoes");

assertTrue(containsBananas.matches(s));
assertFalse(containsMangoes.matches(s));
  • λŒ€κ°œ λ§€μ²˜λŠ” μ§μ ‘μ μœΌλ‘œ μΈμŠ€ν„΄μŠ€ν™”λ˜μ§€ μ•ŠλŠ”λ‹€. λŒ€μ‹  ν–„ν¬λ ˆμŠ€νŠΈλŠ” 맀처λ₯Ό μƒμ„±ν•˜λŠ” μ½”λ“œμ˜ 가독성을 λ†’μ΄κ³ μž λͺ¨λ“  λ§€μ²˜μ— λŒ€ν•œ 정적 νŒ©ν„°λ¦¬ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€.
assertTrue(containsString("bananas").matches(s));
assertFalse(containsString("mangoes").matches(s));
  • ν•˜μ§€λ§Œ μ‹€μ œλ‘œλŠ” 맀처λ₯Ό JUnit의 assertThat()κ³Ό μ‘°ν•©ν•΄ μ‚¬μš©ν•œλ‹€. assertThat()은 맀처의 μžκΈ°μ„œμˆ μ μΈ νŠΉμ„±μ„ ν™œμš©ν•΄ 단정이 μ‹€νŒ¨ν•  경우 뭐가 잘λͺ»λλŠ”지 λΆ„λͺ…ν•˜κ²Œ λ“œλŸ¬λ‚Έλ‹€.
assertThat(s, containsString("bananas"));
assertThat(s, not(containsString("mangoes")));
  • 두 번째 단정은 ν–„ν¬λ ˆμŠ€νŠΈμ˜ κ°€μž₯ μœ μš©ν•œ κΈ°λŠ₯인데 λ°”λ‘œ κΈ°μ‘΄ 맀처λ₯Ό μ‘°ν•©ν•΄ μƒˆλ‘œμš΄ 쑰건을 μ •μ˜ν•˜λŠ” κΈ°λŠ₯이닀. not() λ©”μ„œλ“œλ₯Ό μ „λ‹¬λœ 맀처의 μ˜λ―Έμ™€ λ°˜λŒ€λ˜λŠ” 맀처λ₯Ό μƒμ„±ν•˜λŠ” νŒ©ν„°λ¦¬ ν•¨μˆ˜λ‹€. 맀처λ₯Ό μ‘°ν•©ν•˜λ”λΌλ„ μ½”λ“œμ™€ μ‹€νŒ¨ λ©”μ‹œμ§€ λͺ¨λ‘ μžκΈ°μ„œμˆ μ μΈ νŠΉμ„±μ„ λ κ²Œλ” 섀계돼 μžˆλ‹€.
  • μ½”λ“œλ₯Ό μž‘μ„±ν•΄ λͺ…μ‹œμ μœΌλ‘œ 쑰건을 κ²€μ‚¬ν•˜κ±°λ‚˜ ν’λΆ€ν•œ 정보λ₯Ό λ“œλŸ¬λ‚΄λŠ” 였λ₯˜ λ©”μ‹œμ§€λ₯Ό λ§Œλ“€μ–΄ λ‚΄λŠ” 게 μ•„λ‹ˆλΌ assertThat()에 맀처 ν‘œν˜„μ‹μ„ μ „λ‹¬ν•˜κ³  assertThat()이 κ·Έ 일을 μ•Œμ•„μ„œ μ²˜λ¦¬ν•˜κ²Œ ν•  수 μžˆλ‹€.

πŸ“š jMock2: λͺ© 객체​

  • jMock2λŠ” JUnit에 λΆ™μ—¬μ„œ λͺ© 객체λ₯Ό ν™œμš©ν•œ ν…ŒμŠ€νŠΈ 방식을 μ§€μ›ν•œλ‹€. jMock은 λͺ© 객체λ₯Ό λ™μ μœΌλ‘œ μƒμ„±ν•˜λ―€λ‘œ λͺ©μ„ μƒμ„±ν•˜λ €λŠ” νƒ€μž…μ˜ κ΅¬ν˜„μ²΄λ₯Ό 직접 μž‘μ„±ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€. 또 jMock은 ν…ŒμŠ€νŠΈ λŒ€μƒ 객체 κ°€ 그것과 μƒν˜Έ μž‘μš© 쀑인 λͺ© 객체λ₯Ό μ–΄λ–»κ²Œ ν˜ΈμΆœν•˜κ³  λͺ© 객체가 거기에 λ°˜μ‘ν•΄ μ–΄λ–»κ²Œ λ™μž‘ν•΄μ•Ό 할지λ₯Ό μ§€μ •ν•˜λŠ” κ³ μˆ˜μ€€ APIλ₯Ό μ œκ³΅ν•œλ‹€.
  • jMock API의 핡심 κ°œλ…μ€ λͺ¨μ‘° 객체와 λͺ© 객체, μ˜ˆμƒ ꡬ문이닀. λͺ¨μ‘° κ°μ²΄λŠ” ν…ŒμŠ€νŠΈ λŒ€μƒ 객체의 μ½˜ν…μŠ€νŠΈ, 즉 그것과 μ΄μ›ƒν•˜λŠ” 객체λ₯Ό ν‘œν˜„ν•œλ‹€. λͺ© κ°μ²΄λŠ” ν…ŒμŠ€νŠΈκ°€ μ‹€ν–‰λ˜λŠ” κ³Όμ •μ—μ„œ ν…ŒμŠ€νŠΈ λŒ€μƒ 객체의 μ‹€μ œ 이웃을 λŒ€μ‹ ν•œλ‹€. μ˜ˆμƒ ꡬ문은 ν…ŒμŠ€νŠΈ κ³Όμ •μ—μ„œ ν…ŒμŠ€νŠΈ λŒ€μƒ 객체가 κ·Έκ²ƒμ˜ 이웃을 μ–΄λ–»κ²Œ ν˜ΈμΆœν•΄μ•Ό ν•˜λŠ”μ§€ κΈ°μˆ ν•œλ‹€.
  • λ‹€μŒ μ˜ˆμ œλŠ” AuctionMessageTranslatorκ°€ μ „λ‹¬λœ λ©”μ‹œμ§€ ν…μŠ€νŠΈλ₯Ό ꡬ문 뢄석(parse)ν•΄μ„œ auctionClosed() 이벀트λ₯Ό 생성할 κ²ƒμœΌλ‘œ λ‹¨μ •ν•œλ‹€.
@RunWith(JMock.class)
public class AuctionMessageTranslatorTest {
private final Mockery context = new JUnit4Mockery();
private final AuctionEventListener listener = context.mock(AuctionEventListener.class);
private final AuctionMessageTranslator translator = new AuctionMessageTranslator(listener);

@Test public void
notifiesAuctionClosedWhenCloseMessageReceived() {
Message message = new Message();
message.setBody("SOLVersion: 1.1; Event: CLOSE;");

context.checking(new Expectations() {{
// 단일 μ˜ˆμƒ ꡬ문
// auctionClosed λ©”μ„œλ“œκ°€ μ •ν™•νžˆ ν•œ 번 호좜될 κ²ƒμœΌλ‘œ μ˜ˆμƒ
oneOf(listener).auctionClosed();
}});

translator.processMessage(UNUSED_CHAT, message);
}
}

🎈 μ˜ˆμƒ ꡬ문​

  • jMock의 μ˜ˆμƒ ꡬ문 APIλŠ” ν‘œν˜„λ ₯이 맀우 λ›°μ–΄λ‚˜λ‹€.
  • μ˜ˆμƒ ꡬ문 블둝은 ν•΄λ‹Ή μ˜ˆμ™Έ 블둝을 λ‘˜λŸ¬μ‹Ό ν…ŒμŠ€νŠΈ μ½”λ“œμ™€ κ΅¬λ³„ν•¨μœΌλ‘œμ¨ 이웃 객체가 μ–΄λ–»κ²Œ ν˜ΈμΆœλ˜λŠ”μ§€ κΈ°μˆ ν•˜λŠ” μ½”λ“œμ™€ μ‹€μ œλ‘œ 객체λ₯Ό ν˜ΈμΆœν•˜κ³  κ²°κ³Όλ₯Ό κ²€μ‚¬ν•˜λŠ” μ½”λ“œλ₯Ό λΆ„λͺ…ν•˜κ²Œ κ΅¬λΆ„ν•˜λŠ” 데 λͺ©μ μ΄ μžˆλ‹€.