본문 바로가기
공부/TDD

[OKKYCON: 2018] 자바지기(박재성)- TDD 의식적으로 연습하기

by Moonsc 2020. 9. 20.
728x90

의식적인 연습이란?

많은 연습은 역량을 보장하지 않는다. 그렇기에 온전히 집중하고 '의식적' 으로 행동할 것을 요구한다.

  • 컴포트 존을 벗아난 지점에서 진행, 자신의 현재 능력을 살짝 넘어가는 작업을 지속적으로 시도한다.
  • 명확하고 구체적인 목표를 가지고 진행한다.
  • 피드백과 피드백에 따른 행동 변경을 수반한다. (코드리뷰, 짝 프로그래밍)
  • 기존에 습득한 기술의 특정 부분을 집중적으로 개선함으로써 발전시키고, 수정하는 과정을 거친다.

자바 코드 컨벤션을 지키면서 프로그래밍한다.

indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 

  • while 문 안에 if문이 있으면 들여쓰기는 2이다.
  • indent depth를 줄이는 좋은 방법은 함수를 분리하는 것이다.

함수가 한 가지 일만 하도록 최대한 작게 만든다.

 

자바지기님이 의식적인 연습

미션부여 -> 미션 완료후 PR -> PR 평가 후 공통 피드백 -> 미션2 제약사항 추가(리팩토링) -> 평가 -> 미션3 제약사항 추가 ...

리팩토링 자체가 의식적인 연습으로 TDD를 하는 과정이며, 그 과정에서 극단적인 미션을 추가하여 더욱 의식적인 연습을 돕는다.

 

TDD 리팩토링 (method)

 TDD. 리팩토링 == 운동 : 평생동안 연습하겠다는 마음가짐으로 시작한다.

시작하기

  • 주변 정리를 통해 꾸준히 연습할 시간을 확보한다. 
  • 토이 프로젝트를 시작하여 주변 환경에 영향을 받지 않고 꾸준히 연습한다. 작더라도 꾸준히!

 1단계 - 단위 테스트로 연습하기

/* 동작 과정 테스트! */

@Test
public void split(){
    String[] valeus = "1".split(",");
    assertThat(values).contains("1");
    values = "1,2".split(",");
    assertThat(values).ContainsExactly("1","2");
}

@Test
public void substring(){
    String input = "(1,2)";
    String result = input.substring(1, input.length() - 1);
    assertThat(result).isEqualsTo("1,2");
}

@Test
public void arrayList(){
    ArrayList<String> list = new ArrayList<>();
    list.add("first");
    list.add("second");
    assertThat(list.add("third")).isTrue();
    assertThat(list.size()).isEqualsTo(3);
    assertThat(list.get(0)).isEqualsTo("first");
    contains... remove... size...

}

 

  • 단위 테스트 방법을 학습할 수 있다.
  • 단위 테스트 도구의 사용법을 익힐 수 있다.
  • 사용하는 API에 대한 학습 효과가 있다.

 

2단계 - TDD 연습

  • TDD 연습이 목적이니 난이도가 낮거나 자신에게 익숙한 문제로 시작한다.
  • 엄청 간단한 예제도 상당히 많은 연습을 할 수 있으며 요구사항이 복잡할 필요는 없다.
  • 의존관계를 가지지 않는 요구사항으로 연습한다. ex) 웹, 모바일, DB

 

package string_calc;

public class StringCalculator {

    public static int splitAndSum(String text){
        int result = 0;
        if(text == null ||text.isEmpty()){
            result = 0;
        } else {
            String[] values = text.split(",|:");
            for(String value : values){
                result += Integer.parseInt(value);
            }
        }
        return result;
    }
}

 

@Test
public void null_또는_빈값(){
    assertThat(StringCalculator.splitAndSum(null)).isEqualsTo(0);
    assertThat(StringCalculator.splitAndSum("")).isEqualsTo(0);
}

@Test
public void 값_하나(){
    assertThat(StringCalculator.splitAndSum("1")).isEqualsTo(1);
}

@Test
public void 쉼표_구분자(){
    assertThat(StringCalculator.splitAndSum("1,2")).isEqualsTo(3);
}

@Test
public void 쉼표_콜론_구분자(){
    assertThat(StringCalculator.splitAndSum("1,2:3")).isEqualsTo(6);
}

 

3단계 - 메소드 분리 리팩토링

  • 테스트 코드의 변경 없이 테스트 대상 코드를 개선하는 연습을 한다.
  • 정상적인 기준보다는 정량적이고 측정 가능한 방법으로 연습한다.

 

package string_calc;

public class StringCalculator {

    // 1. 들여쓰기(2depth)를 제거하고 else를 제거
    // 2. 메소드에서 한 가지 일만 하도록 변경, 로컬 변수 제거
    // 3. 컴포즈 패턴 메소드 적용. (1단계 추상화)
    public static int splitAndSum(String text) {
        if (isBlank(text)) {
            return 0;
        }
        return sum(toInts(split(text)));
    }

    private static boolean isBlank(String text){
        return text == null || text.isEmpty();
    }

    private static String[] split(String text){
        return text.split(",|:");
    }

    private static int[] toInts(String[] values) {
        int numbers[] = new int[values.length];
        for (int i = 0; i < values.length; i++) {
            numbers[i] = Integer.parseInt(values[i]);
        }
        return numbers;
    }

    private static int sum(int[] numbers) {
        int result = 0;
        for (int number : numbers) {
            result += number;
        }
        return result;
    }

}

 

  • 연습은 극단적인 방법으로 하는 것도 좋다. 단 한번에 한가지 씩 !
  • 메소드의 라인 수를 제한하며 연습하는 것도 좋은 방법이다.

 

TDD 리팩토링 (class)

 

package string_calc;

/* 양수를 받는 숫자 객체 */
public class Positive {

    private int number;

    public Positive(String number){
        this(Integer.parseInt(number));
    }

    public Positive(int number){
        if(number < 0){
            throw new RuntimeException();
        }
        this.number = number;
    }

    public Positive add(Positive other){
        return new Positive(this.number + other.number);
    }

    public int getNumber(){
        return number;
    }


}

 

package string_calc;

public class StringCalculator {

    // 1. 들여쓰기(2depth)를 제거하고 else를 제거
    // 2. 메소드에서 한 가지 일만 하도록 변경, 로컬 변수 제거
    // 3. 컴포즈 패턴 메소드 적용. (1단계 추상화)
    // 4. 음수가 들어오면 RuntimeException, 양수를 받는 숫자 객체 추가
    public static int splitAndSum(String text) {
        if (isBlank(text)) {
            return 0;
        }
        return sum(toInts(split(text)));
    }

    private static boolean isBlank(String text){
        return text == null || text.isEmpty();
    }

    private static String[] split(String text){
        return text.split(",|:");
    }

    private static Positive[] toInts(String[] values) {
        Positive[] positives = new Positive[values.length];
        for (int i = 0; i < values.length; i++) {
            positives[i] = new Positive(values[i]);
        }
        return positives;
    }


    private static int sum(Positive[] numbers) {
        Positive result = new Positive(0);
        for (Positive number : numbers) {
            result = result.add(number);
        }
        return result.getNumber();
    }

}

 

    @Test
    public void 음수(){
        Assertions.assertThrows(RuntimeException.class, ()-> {
            StringCalculator.splitAndSum("-1:2,3");
        });
    }

 

클래스 분리 연습을 위해 활용할 수 있는 원칙

  • 일급 컬렉션을 쓴다. ex) List<Positive>
  • 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다. (극단적 연습)

 

장난감 프로젝트 난이도 높이기

 

  • 점진적으로 요구사항이 복잡한 프로그램을 구현한다.
  • 앞에서 지켰던 기준을 지키면서 프로그래밍 연습을 한다.

 

리팩토링 연습하기 좋은 프로그램 요구사항

  • 게임과 같이 요구사항이 명확한 프로그램으로 연습
  • 의존관계가 없는 프로그램
  • 약간은 복잡한 로직이 있는 프로그램
  • ex) 로또, 사다리 타기, 볼링 게임 점수판, 체스 게임, 지뢰 찾기 게임 : UI는 콘솔

 

의존관계 추가를 통한 난이도 높이기 (DB, UI, 웹, 모바일 등등..)

  • 테스트하기 쉬운 코드와 테스트하기 어려운 코드를 분리하는 눈이 필요
  • 테스트하기 어려운 코드를 테스트 하기 쉬운 코드로 설계하는 감이 필요

 

한 단계 더 나아간 연습을 하고 싶다면..?

  • 컴파일 에러를 최소화하면서 리팩토링하기
  • ATDD 기반으로 응용 어플리케이션 개발하기
  • 레거시 어플리케이션에 테스트 코드 추가하여 리팩토링하기

 

 

정리

 

TDD. 리팩토링 적용이 실패하는 이유

TDD, 리팩토링 연습이 충분하지 않는 상태에서 `레거시 어플리케이션에 테스트 코드를 추가해 리팩토링하기` 같은 높은 난이도에 도전한다.

도움이 되는 책

객체지향 생활체조 원칙, 클린 코드

TDD. 리팩토링 연습을 위해 필요한 것은?

  • 조급한 마음 대신 여유롭게..
  • 나만의 토이 프로젝트
  • 같은 과제를 반복적으로 구현할 수 있는 인내력, 꾸준함, 성실함

팀에 TDD를 전파하기 위해선?

  • 내가 맡은 기능 구현에 TDD, 리팩토링 적용 -> 전파
  • 내가 구현한 코드 또는 동료의 관심에서 작은 성공을 맛본다.

리더로써 TDD를 전파하기 위해선?

  • 1:1 공략
  • 팀원이 개선할 부분을 말하고, 해결책을 제안하도록 유도

'공부 > TDD' 카테고리의 다른 글

볼링게임 TDD [2]  (0) 2020.10.02
볼링게임 TDD [1]  (0) 2020.09.30
[OKKYCON: 2018] 이규원 - 당신들의 TDD가 실패하는 이유  (0) 2020.09.26
테스트 주도 개발 [10 - 17장]  (0) 2020.09.12
테스트 주도 개발 [1 - 9장]  (0) 2020.09.05

댓글