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
  • 테스트 목록
  • 사용자 입장에서의 시나리오
  • 작은 단계로 시작
  • 우선 컴파일 되게 만들자!
  • 테스트 통과시켜 보자!
  • 테스트 주기
  • 중복 제거 위한 리팩토링
  • 요약

Was this helpful?

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

다중 통화를 지원하는 Money 객체

테스트 목록

테스트 필요한 해야할 목록을 작성한다.

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

  • $5 * 2 = 10$

사용자 입장에서의 시나리오

테스트를 작성할 때는 오퍼레이션의 완벽한 인터페이스에 대해 상상해보는 것이 좋다.

즉, 오퍼레이션이 외부에서 어떤 식으로 보일 지에 대한 이야기를 테스트 코드로 적고 있는 것이다.

오퍼레이션

객체가 수행할 수 있는 연산을 의미하며, 오퍼레이션에 대한 특정한 구현을 메서드라고 부른다.

다형성을 통해, 한 오퍼레이션은 여러 메서드를 가질 수 있다.

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

    }

작은 단계로 시작

위 테스트는 예기치 못한 부작용을 고려하지 않았고, 금액 계산도 정수형으로 하였다.

하지만, 우선 이러한 문제 사항들은 목록에 추가하고, 작은 단계로 시작해보는 것이 중요하다.

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

  • $5 * 2 = 10$

  • amount를 private으로 만들기

  • Dollar 부작용(side effect)?

  • Money 반올림?

우선 컴파일 되게 만들자!

아직은 컴파일 조차 되지 않는다. 따라서 에러를 보고 하나씩 고쳐나가 보자.

문제 상황을 적어보자.

  • Dollar 클래스가 없음

  • 생성자가 없음

  • times 메서드가 없음

  • amount 필드가 없음

하나씩 해결해보자. 우선 Dollar 클래스를 생성하여 첫 번째 에러를 제거한다.

public class Dollar {
}

하지만 생성자가 없다는 에러와 times() 메서드가 없다는 에러, amount 필드가 없다는 에러 또 다시 발생한다. 순차적으로 하나씩 에러를 제거해보자.

따라서, Dollar 클래스에 생성자를 추가하여 첫 번째 에러를 제거한다.

public class Dollar { 
    public Dollar(int mount) {
        
    }
}

다음으로 times() 메서드를 위해서는 스텁 구현이 필요하며, 이를 통해 에러를 제거한다.

스텁 구현

메서드의 서명부와 반환 값을 적으며, 이 메서드를 호출하는 코드가 컴파일이 될 수 있도록 껍데기가 만들어 주는 것을 의미한다.

public class Dollar {
    public Dollar(int mount) {

    }
    
    public void times(int multiplier) {
        
    }
    
}

마지막으로 amount 필드를 추가하여 에러를 제거한다.

public class Dollar {
    int amount;

    public Dollar(int amount) {

    }

    public void times(int multiplier) {

    }

}

결과적으로 테스트는 실패하지만 컴파일이 되는 것을 확인할 수 있다.

테스트 통과시켜 보자!

원하는 결과는 10 이지만, 실제 결과가 0 이 나온 것을 확인할 수 있다. 테스트에서는 실패했지만, 나아진 것은 '다중 통화 구현'에서 '이 테스트를 통과 시킨 후 나머지 테스트도 통과시키기'로 변형되었다는 것이다.

해당 빨간 막대를 해결하기 위한 최소 작업은 아래와 같다.

public class Dollar {
    int amount = 10;

    public Dollar(int amount) {

    }

    public void times(int multiplier) {

    }

}

드디어 초록 막대를 볼 수 있게 되었다!

테스트 주기

하지만, 진정하고 테스트 주기에서의 현재 자신의 단계를 생각해보자. 테스트 주기는 아래와 같다.

테스트 주기

  1. 작은 테스트를 하나 추가한다.

  2. 모든 테스트를 실행해서 테스트가 실패하는 것을 확인한다.

  3. 조금 수정한다.

  4. 모든 테스트를 실행해서 테스트가 성공하는 것을 확인한다.

  5. 중복을 제거하기 위해 리팩토링을 한다.

중복 제거 위한 리팩토링

의존성과 중복

테스트를 작성하게 되면, 자연스럽게 테스트와 코드 사이에 의존성이 발생하게 된다. 즉, 코드나 테스트 중 한쪽을 수정하면 반드시 다른 한쪽도 수정해야만 한다는 것이다.

의존성이 문제 그 자체라면 중복은 문제의 징후이다. 중복의 가장 흔한 예는 로직의 중복이며, 중복된 로직을 제거하는 가장 좋은 방법은 객체를 이용하는 것이다.

프로그램에서는 중복만 제거해주면 의존성도 제거된다. 이게 바로 TDD의 두 번째 규칙이 존재하는 이유이며, 다음 테스트로 진행하기 전에 중복을 제거하여, 오직 한 가지의 코드 수정을 통해 다음 테스트도 통과되게 만들 가능성을 최대화하는 것이다.

이제 5번을 실행할 단계이다. 하지만 어디에 중복이 존재한단 말인가? 이번 경우에는 코드가 아닌 데이터의 중복이 있는 것을 확인할 수 있다. 코드에서의 amount 필드가 5 * 2 나누어 생각해보면, 테스트에서의 5, 2와 데이터가 중복되는 것을 확인할 수 있다. 하지만 5와 2 를 한번에 제거할 수 있는 방법은 없으나, amount 초기화 단계를 times() 메서드 안으로 옮겨 보자.

public class Dollar {
    int amount;

    public Dollar(int amount) {

    }

    public void times(int multiplier) {
        amount = 5 * 2;

    }

}

테스트는 여전히 통과하며, 테스트 막대 역시 초록색이다. 하지만, 아직도 중복 데이터는 여전히 존재한다. 다음으로 5라는 값을 제거할 수 있을까?? 이건 생성자를 통해서 넘어오는 값이기 때문에 amount 초기화 단계를 변경해볼 수 있을 것이다.

public class Dollar {
    int amount;

    public Dollar(int amount) {
        this.amount = amount;

    }

    public void times(int multiplier) {
        amount = amount * 2;

    }

}

또한, multiplier 의 값이 2이므로 이것도 중복이며, 상수를 multiplier 로 일반화할 수 있을 것이다.

public class Dollar {
    int amount;

    public Dollar(int amount) {
        this.amount = amount;

    }

    public void times(int multiplier) {
        amount = amount * multiplier;

    }

}

결과적으로 times 메서드 안에서의 amount 가 변수도 중복이므로 자바 *= 문법을 통해 간결하게 수정할 수 있다.

public class Dollar {
    int amount;

    public Dollar(int amount) {
        this.amount = amount;

    }

    public void times(int multiplier) {
        amount *= multiplier;

    }

}

아직까지 테스트가 성공하는 것을 확인할 수 있다.

이제 드디어 첫 번째 테스트를 완료하며 완료 표시를 할 수 있게 되었다.

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

  • $5 * 2 = 10$

  • amount를 private으로 만들기

  • Dollar 부작용(side effect)?

  • Money 반올림?

요약

요약해보면, 아래와 같이 정리할 수 있다.

  • 자신이 알고 있는 작업해야 할 테스트 목록을 만든다.

  • 오퍼레이션이 외부에서 어떻게 보이길 원하는 지 말해주는 이야기를 테스트 코드로 표현한다.

  • 돌아가는 코드에서 상수를 변수로 변경하여 점진적으로 일반화한다.

  • 새로운 할일을 한번에 처리하는 대신 할일 목록에 추가하고 넘어간다.

Previous1부Next타락한 객체

Last updated 5 years ago

Was this helpful?