ํ์
์คํฌ๋ฆฝํธ๋ก wordwrap ์ ๊ตฌํ์ด๋ค.
์ฌ๊ธฐ ์ฝ๋๋ฅผ ํ์
์คํฌ๋ฆฝํธ๋ก ๊ตฌํํ๋ค.
Clean Coders Episode 19 Advanced TDD Part 2 Examples
Getting Stuck
TDD๋ฅผ ์ด๋ ์ ๋ ํด ๋ดค๋ค๋ฉด "getting stuck"์ ๊ฒฝํํด ๋ดค์ ๊ฒ์ด๋ค.
"getting stuck"์
- ํ์ฌ ์คํจํ๊ณ ์๋ ํ ์คํธ๋ฅผ ์ฑ๊ณต์ํค๊ธฐ ์ํด ์ ์ง์ (incremental)์ผ๋ก ํ ์ ์๋ ์ผ์ด ์๊ณ
- ํ ์คํธ๋ฅผ ์ฑ๊ณต์ํค๊ธฐ ์ํด์๋ ์์ฃผ ๋ง์ ์์ ํ๋ก๋์ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๊ณ
- ๊ทน๋จ์ ๊ฒฝ์ฐ ์ ์ฒด ์๊ณ ๋ฆฌ์ฆ์ ๋ค์ ์์ฑํด์ผ๋ง
ํ๋ ๊ฒฝ์ฐ๋ฅผ ์ผ์ปซ๋ ๊ธฐ์ ์ ์ฉ์ด์ด๋ค.
"getting stuck"์ ๋ฌธ์ ์ ์งํ๋ก์ ์๋์ ๊ฐ์ ๊ฒฝ์ฐ์ ๋ฐ์ํ๋ค.
- ์๋ชป๋ ํ ์คํธ๋ฅผ ์์ฑํ๊ฑฐ๋
- ํ๋ก๋์ ์ฝ๋๋ฅผ ๋๋ฌด ๊ตฌ์ฒด์ (too specific not generic)ํ๊ฒ ์์ฑํ๊ฑฐ๋
- ์์ ๋ ๋ฌธ์ ๋ฅผ ๋ชจ๋ ํ๊ฑฐ๋
์ด ์์ ์์๋ word wrap์ ๊ฒฝ์ฐ๋ก ์ค๋ช ํ๋ค.
public class WrapperTest {
@Test
public void nothing() {
}
}
@Test public void
should_wrap() {
assertThat(wrap("word word", 4), is("word\nword"));
}
private String wrap(String s, int width) {
return s.replaceAll(" ", "\n");
}
- with little golf game
- ์ต์ํ์ ํ์ดํ์ผ๋ก ์ฑ๊ณตํ๋๋ก ํ๋ค.
assertThat(wrap("a dog", 5), is("a dog"));
์ด ํ ์คํธ๋ฅผ ์ถ๊ฐํ๊ณ ๋๋ ๋ญ ํด์ผ ํ ์ง ๋ฐ๋ก ๋ ์ค๋ฅด์ง ์๋๋ค. ์ด๋ ์กฐ์ฌํด์ผ ํ๋ค. getting stuck๋ ๊ฒ ๊ฐ๊ธฐ ๋๋ฌธ์ด๋ค.
return s.length() > width ? s.replaceAll(" ", "\n") : s;
์ด๋ ๊ฒ ํจ์ผ๋ก์จ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ๋ฏ ํ๋ค.
๊ทผ๋ฐ specific production code๋ก ๋๋ฌ ์์๋ค. generalization factor๋ฅผ ์์๋ค. ๋ง์ผ ๋นจ๋ฆฌ ์ด ๋ฌธ์ ์์ back out(์ฒ ํ, ํํด, ์ทจ์)ํ์ง ์์ผ๋ฉด ์ฐ๋ฆฌ๋ ์๋นํ stuck(๊ผผ์ง ๋ฌ์น ๋ชปํ๋ค)ํ๊ฒ ๋ ๊ฒ์ด๋ค.
assertThat(wrap("a dog with a bone", 6), is("a dog\nwidth a\nbone"));
์ด๋ป๊ฒ ์ด ์ผ์ด์ค๋ฅผ ํจ์ค์ํฌ๊น ?
์ด๋ฏธ ๋ฆ์๋ค. ํ์ถ์ด ๋ถ๊ฐ๋ฅํ๋ค.
์ ๋ง stuck(๊ผผ์ง ๋ฌ์น ๋ชปํ๋) ์ํ์ ๋น ์ก๋ค.
์ด์ ๋ช ํํด ์ก๋ค. ์ด ํ ์คํธ๋ฅผ ํจ์ค์ํค๋ ์ ์ผํ ๋ฐฉ๋ฒ์ ์๊ณ ๋ฆฌ์ฆ์ ๋ค์ ์์ฑํ๋ ๊ฒ ๋ฟ์ด๋ค. ์ง๊ธ๊ป ์์ฑํ ๋ชจ๋ ๊ฒ์ ๋ฒ๋ฆฌ๊ณ ์ฒ์๋ถํฐ ๋ค์ ์์ฑํด์ผ ํ๋ค.
- most degenerate test case๋ฅผ ๋จผ์ ์์ฑํ๊ณ
- ์ด ๋ณต์กํจ ํด์ฆ๋ฅผ ์์ฃผ ์์ ์คํ ์ฉ ์ฌ๋ผ๊ฐ์.
- ๊ฐ ๊ฒฝ์ฐ์์ ํด๋น ํ ์คํธ๋ฅผ ํต๊ณผ์ํค๋๋ก specificํ fix๋ฅผ ํ๋ ๊ฒ์ด ์๋๋ผ
- production code๋ฅผ generalizingํ์ฌ ํ ์คํธ๊ฐ ํต๊ณผ๋๋๋ก ํ์.
assertThat(wrap(null, 1), is(""));
return null; ๋์ return "";๋ก ํ
์คํธ๋ฅผ ์ฑ๊ณต์ํด
assertThat(wrap("", 1), is(""));
์ด์ ์๊ฐ๋๋ most degenerate test case๋ wrap(โxโ, 1)์ด๋ค.
assertThat(wrap("x", 1), is("x"));
private String wrap(String s, int width) {
if(s == null)
return "";
return s;
}
assertThat(wrap(null, 1), is("")); ๊ตฌ์กฐ๊ฐ ๋ฐ๋ณต(3๋ฒ)๋๋ค.
- null -> s, 1 -> width, "" -> expected๋ก extract variable
- assertWraps(s, width, expected)๋ก extract methodํ๋ค.
- ๋์ผํ ๊ตฌ์กฐ๋ฅผ ๋ค๋ฅธ 2 ๋ผ์ธ๋ extract method๊ฐ ์ ์ฉ๋๋๋ก ํ๋ค.
- ๊ทธ๋ฆฌ๊ณ s, width, expected๋ฅผ inlineํ๋ค.
- assertWraps ๋ฉ์๋๋ฅผ should_wrap ํ ์คํธ ๋ฉ์๋ ์๋ก ์ด๋์ํจ๋ค.
private void assertWraps(String s, int width, String expected) {
assertThat(wrap(s, width), is(expected));
}
@Test
public void
should_wrap() {
assertWraps(null, 1, "");
assertWraps("", 1, "");
assertWraps("x", 1, "x");
}
first, simplest condition that can break๋ฅผ ํ ์คํธ ์ผ์ด์ค๋ก ์ถ๊ฐํ๋ค.
add failing test for assertWraps("xx", 1, "x\nx");
assertWraps("xx", 1, "x\nx");
private String wrap(String s, int width) {
if(s == null)
return "";
if(s.length() <= width)
return s;
else
return s.substring(0, width) + "\n" + s.substring(width);
}
add failing test for multiple lines("xxx", 1)
private String wrap(String s, int width) {
if(s == null)
return "";
if(s.length() <= width)
return s;
else
return s.substring(0, width) + "\n" + wrap(s.substring(width), width);
}
space๊ฐ ํฌํจ๋ ๊ฒฝ์ฐ์ ๋ํ ์คํจํ๋ ํ ์คํธ ์ถ๊ฐ
assertWraps("x x", 1, "x\nx");
return s.substring(0, width) + "\n" + wrap(s.substring(width).trim(), width);
trim()์ ์ ์ฉ
- width๊ฐ ๊ณต๋ฐฑ ๋ค์ ์ค๋ ๋จ์ด์ ์ผ๋ถ๋ฅผ ํฌํจํ๋ ๊ฒฝ์ฐ
assertWraps("x xx", 3, "x\nxx");
private String wrap(String s, int width) {
if(s == null)
return "";
if(s.length() <= width)
return s;
else {
int breakPoint = s.lastIndexOf(" ", width);
if(breakPoint == -1)
breakPoint = width;
return s.substring(0, breakPoint) + "\n" + wrap(s.substring(breakPoint).trim(), width);
}
}
assertWraps("four score and seven years ago our fathers brought forth upon this continent", 7, "four\nscore\nand\nseven\nyears\nago our\nfathers\nbrought\nforth\nupon\nthis\ncontine\nnt");