๋ค์ด๊ฐ๋ฉฐ
์๋
ํ์ธ์. ์ฐ์ํํ
ํฌ์ฝ์ค์ ๋ฐฑ์๋ 7๊ธฐ ์จ๋ฌด์
๋๋ค. ์ ๋ ์ฐ์ํํ
ํฌ์ฝ์ค์์ ์ฒ์ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ๋ํด์ ์ ํ๊ฒ ๋์๋๋ฐ์. ๋น์์๋ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ด ์คํ๋ ค ๊ฐ์ฒด์งํฅ์ ์ค๊ณ์ ๋ฐฉํฅ์ฑ์ ์ ์ํ์ง ๋ชปํ๋ฉฐ, ์๊ตฌ์ฌํญ์ ๋ง์กฑํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ ์๋๋ฅผ ๋ฆ์ถ๋ค๊ณ ์๊ฐํ์ต๋๋ค. ํ์ง๋ง ์ด๋ฐ ์๊ฐ์ ๋ฐ๊พธ๊ฒ ๋ ๊ณ๊ธฐ๊ฐ ์๋๋ฐ์. ๋ฐ๋ก ์ฐ์ํํ
ํฌ์ฝ์ค ๋ ๋ฒจ 1 ๋ฏธ์
‘์ฅ๊ธฐ’์์ต๋๋ค.
ํด๋น ๋ฏธ์
์์ ์ฅ๊ธฐ ๋ณด๋์ ๊ธฐ๋ฌผ๊ณผ ์ฌ์ด์ ๊ด๊ณ๋ฅผ ๊ตฌํํ๋ฉด์ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ์ค์ฒํด๋ณด์๋๋ฐ, ์คํ๋ ค ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ด ์ค๊ณ์ ๋ฐฉํฅ์ฑ์ ์ก์์ฃผ๊ณ ์ฑ
์์ ๋ช
ํํ ๋ถ๋ฆฌ๋ฅผ ์ ๋ํ์ต๋๋ค. ์ฌ์ ํ ํ
์คํธ๋ฅผ ์์ฑํ๋ ์๋๋ ๋๋ฆฌ๊ธด ํ์ต๋๋ค๋ง ํ
์คํธ ์์ฑ ๊ณผ์ ์์ ๋ถ์๊ณผ ์ค๊ณ๋ฅผ ํจ๊ป ํ๊ณ ์์๋ ๊ฒ์
๋๋ค.
์ผํธ ๋ฒก์ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์์ ํ
์คํธ ์์ฑ์ ๋๋ ์๊ฐ์ ์ผ๋ถ๋ ‘ํ์ ๋ญ๋น’๋ผ๊ณ ํ์ต๋๋ค. ์ด ๋จ์ด๋ ๋ถ์๊ณผ ์ค๊ณ์ ์ฐ์ด๋ ์๊ฐ์ ์ง์ ํ ๊ฐ์น๋ฅผ ๋ง๋ค์ด๋ด์ง๋ง, ์์ํ๊ฒ ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ ์๊ฐ์ ์ธ์ ๊ฐ ์ ๊ฑฐํ ์ ์๋ ์๊ฐ์ด๋ผ๋ ๋ป์ด์ฃ . ํ์ง๋ง ๋ง์ ๊ฐ๋ฐ์๋ค์ด ๋งํ๋ฏ์ด, ์ฝ๋ฉ ์ค๋ ฅ์ด ํฅ์๋๋ฉด ํ
์คํธ ์์ด๋ ๊ฐ์ ํ์ง์ ๋ฌ์ฑํ ์ ์์ ๊ฒ์
๋๋ค. ๋ฌผ๋ก ๊ทธ ์์ค์ ๋๋ฌํ๊ธฐ๊น์ง๋ ๋ง์ ์ฐ์ต์ด ํ์ํ๊ฒ ์ง์.
์ ๋ ์ด๋ฌํ ๊ฒฝํ์ ํตํด ํ
์คํธ์ ๊ฐ์ฒด์งํฅ์ ๋ํด ๊ด์ฌ์ด ๊น์ด์ก์ต๋๋ค. ๊ทธ๋์ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ๋ํด ๋ชจ๋ฅด์๊ฑฐ๋, ์์ง๋ง ๊ทธ ๊ฐ์น์ ์ฒด๊ฐํด๋ณด์ง ๋ชปํ ๋ถ๋ค์ ์ํด ํด๋น ๊ธ์ ์์ฑํ๊ฒ ๋์์ต๋๋ค.
ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ๋ฌด์์ธ๊ฐ?
ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ(Test-Driven Development, TDD)๋ ์ผํธ ๋ฒก(Kent Beck)์ด 1999๋
์ต์คํธ๋ฆผ ํ๋ก๊ทธ๋๋ฐ์์ ์ ์ํ ๊ฐ๋ฐ๋ก ์
๋๋ค. ์ด๋ ์ํํธ์จ์ด ์๊ตฌ์ฌํญ์ ๋ง์กฑํ๋ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ ์ ํด๋น ์ฝ๋๋ฅผ ํ
์คํธํ๋ ์ฝ๋๋ฅผ ๋จผ์ ์์ฑํ๋ ๊ฒ์
๋๋ค. ๊ฐํน ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ์ข์ ์ค๊ณ ๊ธฐ๋ฒ ์ค ํ๋๋ก ๋ณด๊ธฐ๋ ํ์ง๋ง, ์ผํธ ๋ฒก์ '**์ค๊ณ ํผ๋๋ฐฑ**'์ด๋ผ๊ณ ํํํฉ๋๋ค. ์ฆ, ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ํ์ฌ ์ค๊ณ์ ํ์ง์ ํ๊ฐํ๊ณ ๊ฐ์ ๋ฐฉํฅ์ ์๋ ค์ฃผ๋ ๋๊ตฌ์ธ ๊ฒ์
๋๋ค.
ํ
์คํธ๋ฅผ ์์ฑํ๊ธฐ ์ด๋ ต๋ค๋ฉด ์ค๊ณ์ ๋ฌธ์ ๊ฐ ์๋ค๋ ์ ํธ์ด๋ฉฐ, ์ด๋ฅผ ํตํด ์ฐ๋ฆฌ๋ ์ค๊ณ๋ฅผ ๊ฐ์ ํ ์ ์์ต๋๋ค. ํ์ง๋ง, ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ ์์ฒด๊ฐ ์ข์ ์ค๊ณ๋ฅผ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ์ง ์์ต๋๋ค. ๋ชจ๋ ์ค๊ณ์ ๋ํ ๊ฒฐ์ ์ ๊ฐ๋ฐ์ ๋ณธ์ธ์๊ฒ ์๋ ๊ฒ์ด์ฃ .
์ผํธ ๋ฒก์ ๋ก ์ ํ๋ฆฌ์ฆ์ ๋ง์ธ ‘์๋ํ๋ ๊น๋ํ ์ฝ๋(clean code that works)’ ์ด ํ๋ง๋๊ฐ ๋ฐ๋ก ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ๊ถ๊ทน์ ์ธ ๋ชฉํ๋ผ๊ณ ๋งํ์ต๋๋ค. ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ํ๋์ ์ค๊ณ ํผ๋๋ฐฑ์ผ๋ก ์ ํ์ฉํ๋ค๋ฉด ์ด๋ฌํ ๊ถ๊ทน์ ์ธ ๋ชฉํ๋ฅผ ์ ๋ฌ์ฑํ ์ ์์ต๋๋ค. ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์์๋
- ํ ์คํธ๊ฐ ์คํจํ ๊ฒฝ์ฐ์๋ง ์๋ก์ด ์ฝ๋๋ฅผ ์์ฑํ๋ค.
- ์ค๋ณต์ ์ ๊ฑฐํ๋ค.
์ด ๋ ๊ฐ์ง์ ๋จ์ํ ๊ท์น๋ง ๋ฐ๋ฆ ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด๋ฌํ ๊ท์น์ ํตํด ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ํต์ฌ์ธ ‘Red-Green-Refactor’ ์ฌ์ดํด์ด ๊ฒฐ์ ๋ฉ๋๋ค. ํด๋น ์ฌ์ดํด์ ๋ค์๊ณผ ๊ฐ์ ์์๋ก ์ด๋ฃจ์ด์ง๋๋ค.
- Red ใ ก ์คํจํ๋ ์์ ํ ์คํธ๋ฅผ ์์ฑํ๋ค.
- Green ใ ก ์ต๋ํ ๋น ๋ฅด๊ฒ ํ ์คํธ๋ฅผ ํต๊ณผํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
- Refactor ใ ก Red-Green ๊ณผ์ ์์ ์๊ฒจ๋ ์ค๋ณต ์ฝ๋๋ฅผ ์ ๊ฑฐํ๋ค.
์ฌ๊ธฐ์ ์ฃผ๋ชฉํด์ผ ํ ๊ฒ์ ‘์์ ํ
์คํธ’์ ‘์ต๋ํ ๋น ๋ฅด๊ฒ’์
๋๋ค. ์ด๋ฅผ ์ผํธ ๋ฒก์ ์ฉ๊ธฐ๋ผ๊ณ ํํํ๋๋ฐ์. ์๋ฒฝํ ์ค๊ณ๋ฅผ ํ ๋ฒ์ ๋ง๋ค์ด์ผ ํ๋ค๋ ๋ถ๋ด์์ ๋ฒ์ด๋, ์์ ๋จ๊ณ๋ถํฐ ๋์๊ฐ๋ ์ฉ๊ธฐ์
๋๋ค. ์ด๊ฒ์ด ํ๋ก๊ทธ๋๋ฐ์ ํ๋ฉฐ ๋ํ๋๋ ๋ถํ์ค์ฑ์ ๋ํ ๋๋ ค์์ ์ด๊ฒจ๋ด๋ ๋ฐฉ๋ฒ์
๋๋ค.
์ด์ ๋์ ์ค๋ช
์ด๋ฉด ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ๋ํ ์ ๋ฐ์ ์ธ ์ฒ ํ์ ๋ํ ์ค๋ช
์ ๋์ด ๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ง๊ธ๋ถํฐ๋ ํ์๊ฐ์
API๋ฅผ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ๋ก ๊ตฌํํ๋ ์์ ๋ฅผ ํจ๊ป ๋ณด๊ฒ ์ต๋๋ค.
ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ ์ค์ต - ํ์๊ฐ์ API
๊ฐ๋จํ๊ฒ ์คํ๋ง์ ํตํ ํ์๊ฐ์
API๋ฅผ ๋ง๋๋ ์๊ตฌ์ฌํญ์ ํ ๋น ๋ฐ์๋ค๊ณ ํ๊ฒ ์ต๋๋ค.
์๊ตฌ์ฌํญ
- ์ฌ์ฉ์๋ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ก ํ์๊ฐ์ ํ ์ ์๋ค.
- ์ด๋ฉ์ผ์ ์ค๋ณต๋ ์ ์๋ค.
- ๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํ๋ค.
์ฌ์ดํด 1: Member ์ํฐํฐ ์์ฑ
Red ๐
๊ฐ์ฅ ์์ ๋จ์์ด๋ฉด์ ์์ ๋๋ฉ์ธ ๊ฐ์ฒด์ธ Member ์ํฐํฐ๋ฅผ ์์ฑํ๋ ๊ฒ๋ถํฐ ์์ํด๋ณด๊ฒ ์ต๋๋ค. Member๋ ์ด๋ฉ์ผ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ํตํด ์์ฑ๋๋ฏ๋ก ๋ค์๊ณผ ๊ฐ์ด ํ ์คํธ๋ฅผ ์์ฑํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค.
public class MemberTest {
@Test
void ์ด๋ฉ์ผ๊ณผ_๋น๋ฐ๋ฒํธ๋ก_ํ์์_์์ฑํ๋ค() {
// given
String email = "member@email.com";
String password = "password123";
// when
Member member = new Member(email, password);
// then
assertThat(member.getEmail()).isEqualTo(email);
assertThat(member.getPassword()).isEqualTo(password);
}
}
ํ์ฌ ์ด๋ ํ ๊ฐ์ฒด ์์ฑ๋ ์์ด ํ
์คํธ ์ฝ๋๋ง ์์ฑํ๊ธฐ์ ์ปดํ์ผ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค. ํ์ง๋ง, ์ผํธ ๋ฒก์ ์ปดํ์ผ ์๋ฌ ๋ํ Red์ ๊ณผ์ ์ผ๋ก ๋ณด๊ณ ์์ต๋๋ค. ๊ทธ๋ผ ์ด์ ์ด๋ฅผ ๋น ๋ฅด๊ฒ ์ฑ๊ณตํ๋๋ก ๊ตฌํํด๋ด
์๋ค.
Green ๐ข
๋น ๋ฅด๊ฒ Member ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ํ ์คํธ ํต๊ณผ๋ฅผ ์ํด ํ์ํ ๋ถ๋ถ๋ค์ ๊ตฌํํ์ต๋๋ค.
public class Member {
private final String email;
private final String password;
public Member(String email, String password) {
this.email = email;
this.password = password;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
}
๊ทธ๋ผ ์ด์ ๋ฆฌํฉํฐ๋ง์ ํด์ผ ํ ๊น์? ํ์ฌ๋ ๋ฆฌํฉํฐ๋ง ํด์ผํ ์ฝ๋๊ฐ ๋ณด์ด์ง ์์ต๋๋ค. ๊ทธ๋ฌ๋ฉด ํด๋น ๊ณผ์ ์ ๋น ๋ฅด๊ฒ ๊ฑด๋๋ฐ๊ณ ๋ค์ ํ
์คํธ๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
์ฌ๊ธฐ์ ๋ ๊ฐ์ ์๊ตฌ์ฌํญ ์ค ์ด๋ค ๊ฒ์ ํ
์คํธ๋ก ์์ฑํ ์ ์์๊น์? ์ด๋ฉ์ผ์ ์ค๋ณต๋ ์ ์๋ ์๊ตฌ์ฌํญ์ Member ๊ฐ์ฒด๋ง์ผ๋ก๋ ํ๋จํ ์ ์์ต๋๋ค. ํ์ง๋ง, ๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํ๋ค๋ ์๊ตฌ์ฌํญ์ Member ๊ฐ์ฒด๋ง์ผ๋ก ํ๋จํ ์ ์๊ฒ ๋ค์. ์ด๋ฅผ ํ
์คํธ ์ฝ๋๋ก ์์ฑํด๋ด
์๋ค.
์ฌ์ดํด 2: ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ
Red ๐
ํ ์คํธ๋ฅผ 8์ ์ด์ ๋๋ ๋ฏธ๋ง ๋ ๊ฐ์ง ์ผ์ด์ค๋ก ๋๋ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค.
@Test
void ๋น๋ฐ๋ฒํธ๊ฐ_8์_๋ฏธ๋ง์ด๋ฉด_์์ธ๊ฐ_๋ฐ์ํ๋ค() {
// given
String email = "member@email.com";
String shortPassword = "short"; // 5์
// when-then
assertThatThrownBy(() -> new Member(email, shortPassword))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค.");
}
@Test
void ๋น๋ฐ๋ฒํธ๊ฐ_8์_์ด์์ด๋ฉด_ํ์์ด_์์ฑ๋๋ค() {
// given
String email = "member@email.com";
String validPassword = "password"; // ์ ํํ 8์
// when
Member member = new Member(email, validPassword);
// then
assertThat(member).isNotNull();
}
8์ ์ด์์ธ ๊ฒฝ์ฐ Member๊ฐ ์์ฑ๋๋ค๋ ํ
์คํธ๋ ์ด๋ฏธ 1๋จ๊ณ์์ ์์ฑ์ด ๋์๋ค๊ณ ์๊ฐํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค. ํ์ง๋ง, 1๋จ๊ณ์ ํ
์คํธ๋ 8์ ์ด๊ณผ์ ๋น๋ฐ๋ฒํธ๋ก ์์ฑํ๊ณ ์๊ธฐ์ ๋น๋ฐ๋ฒํธ๊ฐ ์ ํํ 8์์ธ ๊ฒฝ๊ณ๊ฐ ํ
์คํธ๋ฅผ ์ถ๊ฐ๋ก ์์ฑํด์ฃผ์์ต๋๋ค.
8์ ๋ฏธ๋ง์ธ ๋น๋ฐ๋ฒํธ ํ
์คํธ์ ๊ฒฝ์ฐ์๋ Member ๊ฐ์ฒด ์์ฑ ์ ์์ธ๊ฐ ๋์ ธ์ง ์ ์๊ฒ ์ฃ . ์ฌ๊ธฐ์ ์ ์ ํ ์์ธ๋ IllegalArgumentException์ด๋ผ ์๊ฐํด ์ด๋ฅผ ๊ฒ์ฆํ๊ณ , ๋ฉ์์ง๊น์ง ์ถ๊ฐ๋ก ํ์ธํด์ค๋๋ค. ์ด์ ํด๋น ํ
์คํธ๋ฅผ ์คํ์์ผ๋ณด๋ฉด ์คํจํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
๋ค์ ๋จ๊ณ๋ ๋น ๋ฅด๊ฒ ์๋ํ๋ ์ฝ๋๋ฅผ ๋ง๋๋ ๊ฒ์
๋๋ค.
Green ๐ข
๋น ๋ฅด๊ฒ ๋์ํ๋ ์ฝ๋๋ Member ๊ฐ์ฒด์ ์์ฑ์์์ ๋น๋ฐ๋ฒํธ ๊ธธ์ด๋ฅผ ๊ฒ์ฆํ๋ ๊ฒ์ผ๋ก ๊ตฌํํ ์ ์์ต๋๋ค.
public Member(String email, String password) {
if (password.length() < 8) {
throw new IllegalArgumentException("๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค.");
}
this.email = email;
this.password = password;
}
์์ฑ์๋ฅผ ์์ ๊ฐ์ด ๋ณ๊ฒฝํ๊ณ ์คํํด๋ณด๋ฉด ์ ์ฒด ํ
์คํธ๊ฐ ์ ์ฑ๊ณตํ ๋ชจ์ต์ ํ์ธํ ์ ์์ต๋๋ค. ์ด์ ๋ฆฌํฉํฐ๋ง ๋จ๊ณ๋ก ๋์ด๊ฐ๋ ๋ ๊ฒ ๊ฐ์ต๋๋ค. ๊ทธ ์ด์ ๋ Member ์์ฑ์ ๋ก์ง์์ if๋ฌธ์ผ๋ก ๋์ด์๋ ๋ถ๋ถ์ ๋ฉ์๋๋ก ๋ถ๋ฆฌํด๋ณผ ์ ์์ ๊ฒ ๊ฐ๊ธฐ ๋๋ฌธ์
๋๋ค.
Refactor โ๏ธ
Member ๊ฐ์ฒด์ ์ฝ๋๋ฅผ ๋ค์ ํ๋ฒ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
public class Member {
private String email;
private String password;
public Member(String email, String password) {
if (password.length() < 8) {
throw new IllegalArgumentException("๋น๋ฐ๋ฒํธ๋ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค.");
}
this.email = email;
this.password = password;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
}
์ฌ๊ธฐ์๋ ๋ฆฌํฉํฐ๋ง ํฌ์ธํธ๊ฐ 3๊ฐ ๋ณด์
๋๋ค.
- ๋งค์ง ๋๋ฒ๋ฅผ ์์๋ก ์ถ์ถ
- ๊ฒ์ฆ ๋ก์ง์ ๋ณ๋ ๋ฉ์๋๋ก ๋ถ๋ฆฌ
- ์์ฑ์๋ ๊ฐ์ฒด ์ด๊ธฐํ์ ์ง์ค
์ด ์ธ ๊ฐ์ง๋ฅผ ๋ฐ์ํ๋ฉด ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
public class Member {
public static final int PASSWORD_LENGTH = 8;
private final String email;
private final String password;
public Member(String email, String password) {
validatePasswordLength(password);
this.email = email;
this.password = password;
}
private void validatePasswordLength(String password) {
if (password.length() < PASSWORD_LENGTH) {
throw new IllegalArgumentException();
}
}
// ...
}
์ด์ ๋๋ฉด Member ๊ฐ์ฒด๊ฐ ์ ๋ฆฌํฉํฐ๋ง ๋์๋ค๊ณ ํ๋จ๋๋๋ฐ์. ์ด์ ๋ค๋ฅธ ์๊ตฌ์ฌํญ์ธ ‘์ด๋ฉ์ผ์ ์ค๋ณต๋ ์ ์๋ค.’๋ฅผ ํ
์คํธ ํด๋ณด๊ฒ ์ต๋๋ค.
์ฌ์ดํด 3: ์ด๋ฉ์ผ ์ค๋ณต ๊ฒ์ฆ
Red ๐
์ฌ๊ธฐ์๋ถํฐ๋ ์คํ๋ง๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋์ด ํ์ํฉ๋๋ค. ์๋ํ๋ฉด ์ด๋ฉ์ผ ์ค๋ณต ์ฌ๋ถ๋ Member ๋จ์ผ ๊ฐ์ฒด๋ก๋ง ํ๋จํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค. ๋ฐ๋ผ์ ์์ฐ์ค๋ฝ๊ฒ ๋๋ฉ์ธ ๊ฐ์ฒด๋ฅผ ๋์ด, ์์ ๋ ์ด์ด๋ก ํ์ฅํ ํ์๊ฐ ์์ต๋๋ค.
์ด๋ฅผ ์ํด ํ์๊ฐ์
์ ์ํํ๋ MemberService์ ๋ํ ํ
์คํธ๋ฅผ ์์ฑํด๋ณด๊ฒ ์ต๋๋ค.
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class MemberServiceTest {
@Autowired
private MemberService memberService;
@Autowired
private MemberRepository memberRepository;
@Test
void ํ์๊ฐ์
_์_์ด๋ฉ์ผ์_์ค๋ณต๋ _์_์๋ค() {
// given
var email = "duplicate@email.com";
memberRepository.save(new Member(email, "password123"));
// when - then
assertThatThrownBy(() -> memberService.register(email, "password456"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("์ด๋ฏธ ์กด์ฌํ๋ ์ด๋ฉ์ผ์
๋๋ค: " + email);
}
@Test
void ์ ํจํ_์ ๋ณด๋ก_ํ์๊ฐ์
์_์ฑ๊ณตํ๋ค() {
// given
String email = "new@email.com";
String password = "password123";
// when
Long memberId = memberService.register(email, password);
// then
Member savedMember = memberRepository.findById(memberId)
.orElseThrow();
assertThat(savedMember.getEmail()).isEqualTo(email);
}
}
Member์ ์ ์ฅ์ ์ํ MemberRepository์ ํจ๊ป ํ์๊ฐ์
์ ์ฑ๊ณตํ๋ ํ
์คํธ์ ์ด๋ฉ์ผ ์ค๋ณต์ผ๋ก ์ธํด ์คํจํ๋ ํ
์คํธ ๋ ๊ฐ์ง๋ฅผ ์์ฑํ์ต๋๋ค. ์ด๋ฅผ ์ฑ๊ณต์ผ๋ก ๋ง๋ค๊ธฐ ์ํด์ ๊ฝค ๋ง์ ๊ฐ์ฒด ์์ฑ ๋ฐ ๋ณ๊ฒฝ์ด ํ์ํฉ๋๋ค.
Green ๐ข
๊ฐ์ฅ ๋จผ์ Member๋ฅผ ์ํฐํฐ๋ก ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
@Entity
public class Member {
public static final int MIN_PASSWORD_LENGTH = 8;
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
private String password;
protected Member() {
}
public Member(String email, String password) {
this(null, email, password);
}
public Member(Long id, String email, String password) {
validatePasswordLength(password);
this.id = id;
this.email = email;
this.password = password;
}
// ...
public Long getId() {
return id;
}
// ...
}
์ด์ MemberService์ MemberRepository๋ฅผ ๊ตฌํํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
@Service
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Transactional
public Long register(String email, String password) {
if (memberRepository.existsByEmail(email)) {
throw new IllegalArgumentException("์ด๋ฏธ ์กด์ฌํ๋ ์ด๋ฉ์ผ์
๋๋ค: " + email);
}
Member member = new Member(email, password);
Member savedMember = memberRepository.save(member);
return savedMember.getId();
}
}
public interface MemberRepository extends Repository<Member, Long> {
Member save(Member member);
Optional<Member> findById(Long id);
boolean existsByEmail(String email);
}
๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐ๊ฒฐ์ ์ํด ๊ฐ๋จํ ์ธ๋ฉ๋ชจ๋ฆฌ H2๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
์ง๊ธ๋ถํด JPA์ ์ฝ๋๋ค์ด ์ฐ๋๋๋ฉด์ ๊ทธ๋ฆฐ์ ๋ง๋ค๊ธฐ ์ํ ๊ณผ์ ์ด ๊ฝค ๊ธธ์ด์ก์ต๋๋ค. ํ์ง๋ง Member ๊ฐ์ฒด์์์ ๋ณํ๋ JPA๋ฅผ ์ํ ์ฐ๋ ๊ณผ์ ์ด์์ ๋ฟ์ด๊ณ , ํ
์คํธ ํ๋ ค๊ณ ํ๋ MemberService์ ๋ก์ง์ ๋ณด๋ฉด ์ฌ์ ํ ์ต๋ํ ๋น ๋ฅด๊ฒ ๊ทธ๋ฆฐ์ ๋ง๋ค์๋ค๋ ์ ์ ํ์ธํ ์ ์์ต๋๋ค.
Refactor โ๏ธ
์ด์ ๋จ์ ์ผ์ MemberService๋ฅผ ๋ฆฌํฉํฐ๋ง ํ๋ ์ฌ์ดํด์
๋๋ค.
์ฌ๊ธฐ์๋ Member์ ๋น์ทํ๊ฒ ๊ฒ์ฆ ๋ก์ง์ ๋ณ๋ ๋ฉ์๋๋ก ๋ถ๋ฆฌํ๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
@Service
public class MemberService {
@Autowired
private MemberRepository memberRepository;
public Member register(String email, String password) {
validateEmailDuplication(email);
Member member = new Member(email, password);
return memberRepository.save(member);
}
private void validateEmailNotDuplicated(String email) {
if (memberRepository.existsByEmail(email)) {
throw new IllegalArgumentException("์ด๋ฏธ ์กด์ฌํ๋ ์ด๋ฉ์ผ์
๋๋ค: " + email);
}
}
}
์ด ๊ธฐ์ธ๋ฅผ ์ด์ด์ Controller๊น์ง ํ์ฅํด๊ฐ๋ฉฐ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ ์ค์ต์ ์ด์ด๊ฐ๋ฉด ์ข๊ฒ ์ง๋ง, ๋ถ๋์ ์ ์ธํ์์ต๋๋ค. ํ์ง๋ง ์ง๊ธ๊น์ง์ ๊ณผ์ ์ ์ ๋ฐ๋ผ์ค์
จ๋ค๋ฉด ํผ์์๋ ์ถฉ๋ถํ ํ์ฅํ ์ ์์๊ฑฐ๋ผ ์๊ฐํฉ๋๋ค.
์ค์ต์์ ํ์ธํ ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ์ด์
์์ฃผ ๊ฐ๋จํ ์์ ์์ง๋ง, ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ์ฌ๋ฏธ๋ฅผ ๋๋ผ์
จ์๊ฑฐ๋ผ ๋ฏฟ์ต๋๋ค. ์ด๋ฅผ ํตํด ์ป์ ์ ์๋ ์ด์ ๋ค์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
์ธํฐํ์ด์ค ์ฐ์ ์ค๊ณ
์์ MemberServiceTest์์ ํ์๊ฐ์
๊ด๋ จ ํ
์คํธ๋ฅผ ์์ฑํ ๋ ‘์ด๋ค ๋ฐฉ์์ผ๋ก ํ์๊ฐ์
๋ก์ง์ ๋จผ์ ์์ฑํ์ง?’๋ผ๋ ๊ณ ๋ฏผ๋ณด๋ค ‘์ด๋ป๊ฒ ํ์๊ฐ์
์ ์ฌ์ฉํ์ง?’๋ผ๋ ๊ณ ๋ฏผ์ ๋จผ์ ํ์ต๋๋ค. ์ด๋ฅผ ํตํด ์ฌ์ฉํ๋ ํด๋ผ์ด์ธํธ์ ๊ด์ ์์ API๋ฅผ ์ค๊ณํ ์ ์์์ด์. ๊ฐ์ฒด์งํฅ์์ ๋งํ๋ ‘**๋ฌป์ง๋ง๊ณ , ์์ผ๋ผ(Tell, Don’t Ask)**’๋ฅผ ์์ฐ์ค๋ฝ๊ฒ ์ค์ฒํ ์ ์๋ ๋๋ชฉ์ด๊ธฐ๋ ํฉ๋๋ค.
์ ์ง์ ์ธ ๋ณต์ก๋ ๊ด๋ฆฌ
์ฃผ์ด์ง ์๊ตฌ์ฌํญ์ ๋ณด๊ณ ํ๋ฒ์ ๊ตฌํํ๋ค๊ณ ์์ํด๋ณด์ธ์. ์ด๋์๋ถํฐ ์์ํด์ผํ ์ง, ์ด๋ค ๋ฐฉ์์ผ๋ก ๊ตฌํํด์ผ ํ ์ง ๊ณ ๋ฏผํ๋ฉฐ ํ์ํ ํด๋์ค๋ฅผ ์ฌ๋ฌ ๊ฐ ์์ฑํ ๊ฒ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ฌ๋ฌ ํด๋์ค๋ค์ ํจ๊ป ๊ตฌํํ๋ฉด์ ์ ์ ๋ณต์กํด์ง๊ฒ ์ฃ . ๊ทธ๋ฌ๋ค๋ณด๋ฉด ํ๋ฆ์ ์๊ธฐ ๋ง๋ จ์
๋๋ค. ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ํด ๋ค์ ์ฌ๋ฌ ํด๋์ค๋ฅผ ์ฐพ์๋ด์ผ ํ๋ ์ํฉ์ด ์๊ธฐ๊ฒ ์ฃ .
ํ์ง๋ง, ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ํตํด ์๊ตฌ์ฌํญ์ ํ๋์ฉ ์ ์ง์ ์ผ๋ก ๊ฐ๋ฐํ๋ฉด์ ํ๋ฆ์ ์ซ์๊ฐ๊ธฐ ์ฌ์์ก์ต๋๋ค.
- Member ์์ฑ
- ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ
- ์ด๋ฉ์ผ ์ค๋ณต ๊ฒ์ฆ
์ด๋ ๊ฒ ๊ฐ ๋จ๊ณ๋ณ๋ก ์๋ํ๋ ์ฝ๋๋ฅผ ์์ฑํ๊ณ , ์ด์ ์ ์๊ตฌ์ฌํญ์ ๋ง์กฑํ๊ธฐ์ ํ์ฌ ๊ฐ๋ฐํ๋ ์๊ตฌ์ฌํญ์ ์ง์คํ ์ ์์์ต๋๋ค.
๋ฆฌํฉํฐ๋ง ๋ด์ฑ
๊ฐ๋จํ ์์ ์ฌ์ ํฐ ์ด์ ์ด ์๋๋ผ๊ณ ์๊ฐํ์ค ์ ์์ง๋ง, ๋ณต์กํ ๋ก์ง์ ๋ฆฌํฉํฐ๋ง ํ๋ค๊ณ ์์ํด๋ณด์ธ์. ํ
์คํธ๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํ์ง ์์๋ค๋ฉด ๋ฆฌํฉํฐ๋ง์ ํ๊ธฐ ๋ฌด์์ ์๊ฒ๋๋ค. ํ์ง๋ง, ์ ์ ์ ์ผ๋ก ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด์ ๋๋ ค์์์ด ์ฝ๋ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ ํ ์ ์๊ฒ ์ฃ .
๋น ๋ฅธ ํผ๋๋ฐฑ
์ค์ต์์ ์ง์์ ์ผ๋ก ํ
์คํธ๋ฅผ ์คํํ๋ฉด์ ์ฝ๋๊ฐ ์ ๋๋ก ๋์ํ๋ค๋ ํ์ ์ ์ป์ ์ ์์์ต๋๋ค. ์ ๋ ์ด๊ฒ์ด ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์์ ๊ฐ์ฅ ํฐ ์ด์ ์ด๋ผ๊ณ ์๊ฐํฉ๋๋ค. ํนํ ์ฌ๋ฆฌ์ ์ธ ์ธก๋ฉด์์๋ ํฐ ๋์์ด ๋ฉ๋๋ค. ์ฝ๋๋ฅผ ์์ฑํ์๋ง์ ์ด๋ก๋ถ์ด ์ผ์ง๋ ๊ฒ์ ํ์ธํ๋ฉด ์๋๊ฐ์ ๋๋ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
๊ทธ๋ฆฌ๊ณ ํ
์คํธ ์ฝ๋๋ฅผ ์์ฑํ๊ณ , ์คํจํ๊ณ , ์ฑ๊ณตํ๋ ๊ณผ์ ์์ฒด์ ์ด๋ฌํ ๋ฆฌ๋ฌ์ด ๋ง์น ๊ฒ์๊ณผ ๊ฐ์์ ์คํ
์ด์ง๋ฅผ ํด๋ฆฌ์ด ํ๋ฏ์ด ์์ ๋ชฉํ๋ฅผ ํ๋์ฉ ๋ฌ์ฑํด ๋๊ฐ๋ ๊ณผ์ ์ด ๊ฐ๋ฐ์ ๋ ์ฌ๋ฐ๊ฒ ๋ง๋ค์ด์ฃผ๊ธฐ๋ ํฉ๋๋ค.
์ด๋ ๊ฒ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ์ด์ ๋ง ๋ณด๋ฉด ๋ง์น ๊ฐ๋ฐ์ ์ํ๊ฒ ๋๋ ๋ง๋ฒ ๊ฐ์ต๋๋ค๋ง ๋น์ฐํ ๋จ์ ๋ ์กด์ฌํฉ๋๋ค.
ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ๋จ์
์๋ชป๋ ํ ์คํธ์ ํจ์
์ฌ์ค ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ํ
์คํธ๋ฅผ ‘์’ ์์ฑํด์ผ ํฉ๋๋ค. ํ
์คํธ๋ฅผ ์๋ชป ์์ฑํ๋ฉด, ์์ ๋ชจ๋ ์ฅ์ ์ด ๋ฌด์๋ฏธํด์ง๊ธฐ ๋๋ฌธ์
๋๋ค. ๊ทธ๋์ ์๋ชป๋ ํ
์คํธ๋ ์คํ๋ ค ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์์ ๋
์ด ๋ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด ๊ตฌํ์ ๊ณผ๋ํ๊ฒ ๊ฒฐํฉ๋ ํ
์คํธ๊ฐ ์์ต๋๋ค. ํด๋น ํ
์คํธ๋ ๋ด๋ถ ๊ตฌํ์ ๋๋ฌด ์์ธํ๊ฒ ํ
์คํธ ํ๋ ๊ฒ์ธ๋ฐ, ์ฝ๋ ๊ตฌ์กฐ๋ฅผ ์กฐ๊ธ๋ง ๋ณ๊ฒฝํด๋ ์๋ง์ ํ
์คํธ๊ฐ ๊นจ์ง๊ฒ ๋ฉ๋๋ค. ์ด๋ฐ ํ
์คํธ๋ ์ฅ์ ์ธ ๋ฆฌํฉํฐ๋ง ๋ด์ฑ์ ๋ฌด์๋ฏธํ๊ฒ ๋ง๋ญ๋๋ค.
์ ์ง๋ณด์ ๋น์ฉ
ํ
์คํธ ์ฝ๋๋ ๊ฒฐ๊ตญ ์ฝ๋์
๋๋ค. ์๊ตฌ์ฌํญ์ด ๋ณ๊ฒฝ๋๋ค๋ฉด, ์๋ง์ ํ
์คํธ ์ฝ๋๋ค๋ ํจ๊ป ๋ณ๊ฒฝ๋์ด์ผ ํ๊ฒ ์ฃ . ์ด๋ ๋ณํ์ ๋ํ ์ ํญ์ผ๋ก ์์ฉํ ์ ์์ต๋๋ค. ๊ธฐ์กด ์ฝ๋๋ฅผ ๊ณ ์น๋ ์๊ฐ๊ณผ ๋๋ถ์ด ํ
์คํธ ์ฝ๋์ ์์ ๋ ํด์ผํ๊ธฐ ๋๋ฌธ์ด์์. ๊ทธ๋ ๊ฒ ๋๋ฉด ์์ฐ์ค๋ฝ๊ฒ ํ
์คํธ ์ฝ๋๋ฅผ ํฌ๊ธฐํ๊ฒ ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ด ์๊น๋๋ค.
๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ …
์ค์กด์ฃผ์์์ ‘๊ทธ๋ผ์๋ ๋ถ๊ตฌํ๊ณ ’๋ ์ธ๊ฐ์ด ๋ถ์์ ํ ํ์ค ์์์๋ ์ค์ค๋ก์ ์ ํ๊ณผ ํ๋์ผ๋ก ์๋ฏธ๋ฅผ ๋ง๋ค์ด๊ฐ๋ค๋ ๋ป์ ๋ด๊ณ ์์ต๋๋ค. ์๋ฒฝํ์ง ์์ ์ธ์ ์์์, ๋ถ์๊ณผ ๋ชจ์์ ์ธ์ ํ๋ฉด์๋ ์ค์ค๋ก์ ์กด์ฌ ์ด์ ๋ฅผ ์ฐพ์๊ฐ๋ ํ๋์ด์ฃ .
ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ ์ญ์ ์ด์ ๋ฎ์ ์์ต๋๋ค. ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ์๋ฒฝํ ์ค๊ณ๋ ์๊ตฌ์ฌํญ์ด ์ฃผ์ด์ง์ง ์์ ๋ถํ์คํ ์ํฉ์์, ์๊ฒ ์คํจํ๊ณ ๋ค์ ๊ณ ์ณ ๋๊ฐ๋ฉฐ ๋ ๋์ ๋ฐฉํฅ์ผ๋ก ๋์๊ฐ๋ ๊ฐ๋ฐ์์ ์ค์ฒ์
๋๋ค. ํ
์คํธ๊ฐ ํญ์ ๊ฐ๋ฐ์ ๋ฐฉํฅ์ ์๋ฒฝํ ๋ณด์ฅํด์ฃผ์ง๋ ์์ง๋ง, ์ฐ๋ฆฌ๋ ํ
์คํธ๋ฅผ ํตํด ๋ถํ์คํ ํ์ค ์์์ ์กฐ๊ธ ๋ ํ์ ์๊ฒ ๋์๊ฐ ์ ์์ต๋๋ค.
๊ฒฐ๊ตญ ์ค์ํ ๊ฒ์ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ๋ชจ๋ ์ํฉ์ ๊ฐ๋ฐ์ ์ผ๋ก ์ ์ฉํ๋ ๊ฒ์ด ์๋๋ผ, **๊ทธ ์ฒ ํ์ ์ดํดํ๊ณ ์ํฉ์ ๋ง๊ฒ ์ ํํ๋ ๊ฒ**์
๋๋ค. ๋ง์น ์ค์กด์ฃผ์์๊ฐ ์ฃผ์ด์ง ๋ณธ์ง์ ์์ํ๊ธฐ๋ณด๋ค ์ค์ค๋ก ์กด์ฌ์ ์๋ฏธ๋ฅผ ๋ง๋ค์ด๊ฐ๋ฏ, ๊ฐ๋ฐ์ ์ญ์ ์ค์ค๋ก์ ํ๋จ๊ณผ ๊ฒฝํ์ ํตํด ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ์์ ๋ง์ ๋ฐฉ์์ผ๋ก ์ค์ฒํด์ผ ํฉ๋๋ค. ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ์๋ฒฝํ ์ฝ๋๋ฅผ ์ํ ๋๊ตฌ๊ฐ ์๋๋ผ, ๋ ๋์ ๊ฐ๋ฐ์๊ฐ ๋๊ธฐ ์ํ **์ฌ๊ณ ์ ํ**์ด๊ธฐ ๋๋ฌธ์
๋๋ค.
์ด๋ฒ ๊ธ์ด ๋จ์ํ ๋ฐฉ๋ฒ๋ก ์ ์๊ฐ๋ฅผ ๋์ด, “๋ถ์์ ํ ํ์ค ์์์๋ ๋ ๋์ ์ฝ๋๋ฅผ ํฅํด ๋์๊ฐ๋ ค๋ ๊ฐ๋ฐ์์ ํ๋”๋ก์ ํ
์คํธ ์ฃผ๋ ๊ฐ๋ฐ์ ๋ค์ ๋ฐ๋ผ๋ณด๋ ๊ณ๊ธฐ๊ฐ ๋์์ผ๋ฉด ํฉ๋๋ค.
์ฐธ๊ณ ์๋ฃ
TDD ๋ฐฐ์์ผ ํ ๊น?
์ค์ ์์ TDDํ๊ธฐ
TDD Isn’t Design
Test-Driven Development: By Example
'๊ฐ๋ฐ > ์ฐ์ํํ ํฌ์ฝ์ค' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ๋ฌธ์์ด ๊ณ์ฐ๊ธฐ๋ฅผ ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ(TDD)๋ก ์งํํ๋ฉฐ ๋๋ ์ (2) | 2025.10.26 |
|---|---|
| BufferedReader์ readLine()์ผ๋ก HTTP ์์ฒญ ํ์ฑ ์ค ์๊ธด ๋ฌธ์ (1) | 2025.09.08 |
| ์ฐํ ์ฝ ๋ ๋ฒจ 2 ํ๊ณ (4) | 2025.06.13 |
| ์ฐํ ์ฝ ๋ ๋ฒจ 2์์์ ์ธ ๋ฒ์งธ ๋ฏธ์ ํ๊ณ (17) | 2025.06.01 |
| ์๋น์ค ํ ์คํธ ๊ฐ์ ํ๊ธฐ (6) | 2025.05.26 |