Developer Log
  • Wiki
  • Books
    • 리눅스 시스템 프로그래밍
      • 핵심 개념 소개
        • 시스템 프로그래밍
        • API와 ABI
        • 리눅스 프로그래밍의 개념
          • 파일과 파일시스템
          • 프로세스
          • 사용자와 그룹
          • 권한
          • 시그널
          • 프로세스간 통신
          • 에러 처리
    • 데이터 베이스 첫걸음
      • 데이터베이스란
        • 데이터베이스의 역할을 생각해 보자
          • 우리와 데이터베이스의 관계
          • 데이터베이스의 기본 기능
          • 데이터베이스 종류
      • 관계형 데이터베이스란
        • 대표적인 DBMS를 알아보자
          • 관계형 데이터베이스란
          • SQL 기초 지식
          • 관계형 데이터베이스를 다루기 위한 사전 지식
      • 데이터베이스에 얽힌 돈 이야기
        • 초기비용과 운영비용을 생각하자
      • 데이터베이스와 아키텍처 구성
        • 다중화에 대해 생각해보자
          • 아키텍처란
          • 데이터베이스의 아키텍처
            • 역사와 개요
              • Stand-alone
              • 클라이언트/서버
              • Web 3계층
            • 가용성과 확장성의 확보
          • DB 서버의 다중화
            • 클러스터링
            • 리플리케이션
          • 성능을 추구하기 위한 다중화 - Shared Nothing
          • 적합한 아키텍처를 설계하기 위해
      • DBMS를 조작할 때 필요한 기본 지식
        • MySQL 설치해보자
        • MySQL과 커넥션 만들기, 데이터베이스에 전화걸기
        • SQL과 관리 명령의 차이
        • 관계형 데이터베이스의 계층
      • SQL 문의 기본
        • SELECT 문으로 테이블 내용을 살펴보자
        • SELECT 문을 응용해보자
        • 데이터를 갱신, 삽입, 제거해보자
        • 뷰를 작성하고 복수 테이블에서 선택해보자
      • 트랜잭션과 동시성 제어
      • 테이블 설계의 기초
      • 백업과 복구
      • 성능을 생각하자
    • 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴
      • 객체 지향
        • 들어가기
        • 객체 지향
        • 다형성과 추상 타입
        • 재사용: 상속보단 조립
      • 설계 원칙 / DI와 서비스 로케이터
        • 설계 원칙: SOLID
          • 단일 책임 원칙(Single Responsibility Principle)
          • 개방 폐쇄 원칙(Open - Closed Principle)
          • 리스코프 치환 원칙(Liskov Substitution Principle)
          • 인터페이스 분리 원칙(Interface Segregation Principle)
          • 의존 역전 원칙(Dependency Inversion Principle)
          • SOLID 정리
        • DI(Dependency Injection)와 서비스 로케이터
      • 주요 디자인 패턴
        • 디지인 패턴이란?
        • 전략(Strategy) 패턴
        • 템플릿 메서드(Template Method) 패턴
        • 상태(State) 패턴
        • 데코레이터(Decorator) 패턴
        • 프록시(Proxy) 패턴
        • 어댑터(Adapter) 패턴
        • 옵저버(Observer) 패턴
        • 미디에이터(Mediator) 패턴
        • 파사드(Facade) 패턴
        • 컴포지트(Composite) 패턴
        • 널(Null) 객체 패턴
        • 팩토리 메서드 패턴
        • 커맨드 패턴
        • 추상 팩토리 패턴
    • 테스트 주도 개발
      • 1부
        • 다중 통화를 지원하는 Money 객체
        • 타락한 객체
        • 모두를 위한 평등
        • 프라이버시
        • 솔직히 말하자면
        • 돌아온 '모두를 위한 평등'
        • 사과와 오렌지
Powered by GitBook
On this page
  • TDD 주기
  • 테스트 작성한다.
  • 실행 가능하게 만든다.
  • 올바르게 만든다.
  • 분할 정복
  • 초록 막대를 보기 위한 전략
  • 요약

Was this helpful?

  1. Books
  2. 테스트 주도 개발
  3. 1부

타락한 객체

TDD 주기

일반적인 TDD 주기는 아래와 같이 진행한다.

테스트 작성한다.

오퍼레이션이 코드에 어떤 식으로 나타나길 원하는 지 생각해보고, 인터페이스 대한 이야기를 작성해보는 작업이다.

실행 가능하게 만든다.

이 단계에서 가장 중요한 것은 빠르게 초록 막대를 보는 것이다. 깔끔하고 단순한 해법이 명백히 보인다면 그것을 입력한다. 만약 깔끔하고 단순한 해법이지만 구현 시간이 몇 분 걸린다면 일단 적어 놓은 뒤 본래의 목적으로 돌아온다.

올바르게 만든다.

이 단계에서 이전에 시스템 동작(초록 막대)을 보기 위해 저질렀던 죄악을 수습한다. 중복을 제거하고 초록 막대로 되돌린다.

분할 정복

TDD 최종 목적은 "작동하는 깔끔한 코드"를 얻는 것이다. 작동하는 깔끔한 코드를 얻는 것은 때로는 최고의 프로그래머들 조차 도달하기 힘든 목표이며, 평범한 프로그래머들에게는 거의 불가능한 일이다. 따라서, "작동하는 깔끔한 코드"를 얻기 위해 문제를 분할하여 접근해보자. 결론부터 말하면, '작동하는 코드'를 만들고 나서 '깔끔한 코드' 로 변모시키는 것이다.

다시 다중화폐 예제에서 Dollar 부작용 테스트 돌아가 보자.

지금까지 테스트 하나를 통과했지만, 몇 가지 부자연스러운 결과를 알 수있다. Dollar 연산 수행시 Dollar 값 바뀐다는 것이다. 그러면 다시 TDD 주기의 "테스트를 작성한다." 단계에서 부터 접근을 해보자.

  • $5 + 10CHF = $10(환율이 2:1 일 경우)

  • $5 * 2 = 10$

  • amount를 private으로 만들기

  • Dollar 부작용(side effect)?

  • Money 반올림?

우선 내가 원하는 시나리오를 적어보자. five Dollar 객체에 2를 곱하고, 3을 각각 곱하면 10, 15가 되는 자연스러운 시나리오이다.

@Test
void testMultiplication() {
    Dollar five = new Dollar(5);
    five.times(2);
    assertEquals(10, five.amount);
    five.times(3);
    assertEquals(15, five.amount);

}

두 번째로 실행 가능하게 만들어보자. 위 두번째 테스트에서 실패하게 된다.

왜일까? 이유는 4 라인을 수행하며 five Dollar 객체의 상태가 변하여 더이상 5가 아니기 때문이다.

그렇다면 times() 에서 새로운 객체를 반환하면 어떨까? 원래의 5달러를 가지고 온종일 곱하기를 수행해도 원래 5달러의 값은 변경되지 않을 것이다.

이를 적용해보기 위기 위해서는 Dollar 인터페이스와 테스트 모두를 변경해야만 하는데 괜찮은 선택인가??

어떤 구현이 올바른가에 대한 우리 추측이 완벽하지 못한 것과 마찬가지로 올바른 인터페이스에 대한 추측 역시 절대 완벽하지 못하다.

따라서, 첫 번째 단계로 돌아가서 위 생각을 반영하여 다시 테스트 코드를 작해보자.

@Test
void testMultiplication() {
    Dollar five = new Dollar(5);
    Dollar ten = five.times(2);
    assertEquals(10, ten.amount);
    Dollar fifteen = five.times(3);
    assertEquals(15, fifteen.amount);

}

역시나 컴파일 에러가 난다. 초록 막대를 보기 위해 에러를 빨리 제거해보자.

times() 반환 타입을 Dollar로 변경하고 Null 값을 반환하여 에러를 제거한다.

    public Dollar times(int multiplier) {
        amount *= multiplier;
        return null;

    }

에러를 제거한 덕분에 컴파일 되지만, 테스트 과정에서 null 값 참조하는 NullPointerException 예외가 발생하게 된다.

NullPointerException 예외를 제거하고 올바른 금액을 갖는 Dollar 를 반환하여 실행 가능하게 만들어야 한다.

public Dollar times(int multiplier) {
    return new Dollar(amount * multiplier);

}

결과적으로 초록 막대를 볼 수 있게 된다.

  • $5 + 10CHF = $10(환율이 2:1 일 경우)

  • $5 * 2 = 10$

  • amount를 private으로 만들기

  • Dollar 부작용(side effect)?

  • Money 반올림?

초록 막대를 보기 위한 전략

이전 과정과 달리, 위 과정은 가짜 구현으로 시작해서 점차 실제 구현으로 바꿔나간 것이 아닌 올바른 구현이라고 생각한 내용으로 바로 실제 구현 과정을 적용하였다. 이와 같이, 최대한 빨리 초록 막대를 보기 위해 취할 수 있는 전략 중 두 가지는 아래와 같다.

  1. 가짜 구현 하기 : 상수를 반환하게 만들고 진짜 코드를 얻을 때까지 단계적으로 상수를 변수로 일반화해 나간다.

  2. 명백한 구현 사용하기 : 실제 구현을 입력한다.

여기서 켄트벡은 보통 실무에서 두 방법을 모두 사용한다고 설명한다. 명백하게 무엇을 구현해야 할 지를 알고 있다면 실제 구현을 입력하고 테스트를 하고, 만약 예상치 못한 빨간 막대를 만나게 된다면 가짜 구현 하기 방법을 사용하면서 올바른 코드로 리팩토링을 한다는 것이다.

마지막 한 가지 전략은 다음장에서 보게 될 삼각측량이라는 방법이다.

요약

이번 장에서 진행해본 것을 요약해 보면 아래와 같다.

  • 설계상의 결함(Dollar 부작용)을 그 결함으로 인해 실패하는 테스트로 변환하였다.

  • 스텁 구현으로 컴파일을 통과하도록 만들었다.

  • 올바르다고 생각하는 코드를 입력하여 테스트를 통과하였다.

느낌(부작용에 대한 혐오감)을 테스트(하나의 Dollar 객체에 곱하기를 두 번 수행하는 것)로 변환하는 것은 TDD의 일반적 주제이다. 이러한 작업을 오래 할수록 미적 판단을 테스트로 담아내는 것에 익숙해지며, 설계 논의에 깊이를 더할 수 있다.

Previous다중 통화를 지원하는 Money 객체Next모두를 위한 평등

Last updated 5 years ago

Was this helpful?