본문 바로가기

TDD, CleanCode

클린코더스 강의 리뷰 5. TDD 예제 (wordWrap)

1. getting stuck.

다음 테스트를 보자.

public class WrapperTest {
    @Test public void
    should_wrap() {
        assertThat(wrap("word word",4),is("word\nword"));
    }

    private String wrap(String s, int length) {
        return null;
    }
}

wrap 은 length를 받아 받은 문자열을 length만큼 자르고 나머지는 다음줄에 표현한다.

 

이 경우에는 replaceAll을 이용하여 문제를 해결 할 수 있다.

    private String wrap(String s, int length) {
        return s.replaceAll(" ","\n");
    }

다른 failing test를 추가하자

        assertThat(wrap("a dog",5),is("a dog"));

이 경우는 다음과 같이 바꾸어야 한다.

    private String wrap(String s, int width) {
        return s.length() > width ? s.replaceAll(" ","\n") : s;
    }

또 다른 failing test 를 추가하자.

        assertThat(wrap("a dog with a bone", 6), is("a dog\nwidth a\nbone"));

다음 코드를 수정하려니 어떻게 해야할지 막히게 된다.

이런 경우를 stuck 상태에 빠졌다고 한다.

이 문제를 해결하기 위해서는 다시 처음부터 해야 한다고 한다.

 

발생 원인은 

처음부터 generate한 예시와 코드를 작성했기 때문이다.

 

이를 방지하기 위해서 다음과 같이 코드 하라고 한다.

Getting Unstuck

  • most degenerate test case를 먼저 작성하고
  • 이 복잡함 퀴즈를 아주 작은 스텝씩 올라가자.
  • 각 경우에서 해당 테스트를 통과시키도록 specific한 fix를 하는 것이 아니라
  • production code를 generalizing하여 테스트가 통과되도록 하자.

2. 다시 처음부터 예시를 살펴 보자!

1. 가장 쉽고 일반적인 것부터 (null 과 공백)

public class WrapperTest {
    @Test public void
    should_wrap() {
        assertThat(wrap(null,1),is(""));
        assertThat(wrap("",1),is(""));
    }

    private String wrap(String s, int width) {
        return "";
    }
}

 

2. 간단한 케이스 추가(공백이 필요 없는 문자들)

public class WrapperTest {
    @Test public void
    should_wrap() {
        assertThat(wrap("",1),is(""));
        assertThat(wrap("",1),is(""));
        assertThat(wrap("x",1),is("x"));
    }

    private String wrap(String s, int width) {
        if(s==null)
            return "";
        return s;
    }
}

<리팩토링한 케이스>

    @Test public void
    should_wrap() {
        assertWraps(null, 1, "");
        assertWraps("", 1, "");
        assertWraps("x", 1, "x");
    }

    private void assertWraps(String s, int width, String expected) {
        assertThat(wrap(s, width), is(expected));
    }

 

3. 공백이 필요한 케이스

    @Test public void
    should_wrap() {
        assertWraps(null, 1, "");
        assertWraps("", 1, "");
        assertWraps("x", 1, "x");
        assertWraps("xx", 1, "x\nx");
    }

    private void assertWraps(String s, int width, String expected) {
        assertThat(wrap(s, width), is(expected));
    }

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

substring으로 width만큼 짜른 후에 \n를 한다.

 

4. 공백이 여러개인 케이스

public class WrapperTest {
    @Test public void
    should_wrap() {
        assertWraps("xxx", 1, "x\nx\nx");
    }

    private String wrap(String s, int width) {
        if(s.length() <= width)
            return s;
        else
            return s.substring(0,width) + "\n" + wrap(s.substring(width),width);
    }

재귀 함수를 이용하여 width가 s.elngth() <= width에 걸릴 때 까지 반복시킬 수 있다.

 

5. 특이한 변칙적인 케이스

다음 케이스일 경우에는 x\nx를 기대하지만 공백도 하나의 문자로 취급하고 x\n\nx\n와 같은 결과가 나온다.

public class WrapperTest {
    @Test public void
    should_wrap() {

        assertWraps("x x", 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" + wrap(s.substring(width).trim(),width);
    }
}

trim을 통해 이를 처리 한다.

 

6. 더 특이한 케이스

public class WrapperTest {
    @Test public void
    should_wrap() {

        assertWraps("x xx", 3, "x\nxx");
    }
    
    private String wrap(String s, int width) {
        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);
        }
    }
}

만약 width 이전에 space(공백)이 나온 경우에는 width의 상관없이 바로 공백 한 후 width를 다시 세서 공백을 만들어 줘야 한다.

이 경우에 lastIndexOf를 이용하여 공백을 넣을 부분을 찾는다.

lastIndexOf의 첫번째 파라미터는 찾을 문자열 두번째는 어디서 부터 시작할지이다.

x xx경우

x\nx 

1 2 3 인 경우이고 3번 x부터 뒤에서 몇번째인지 찾는다. 이 경우 breakPoint는 결국 1이 된다.

만약 찾을 수 없다면 -1를 반환한다.

 

7. 원하는 테스트

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

제대로 작성 됬다면 정상적으로 동작하게 될 것이다.