본문 바로가기

IT Book Summary/TDD

29장 - 31장

29장 xUnit 패턴

 

단언(assertion)

테스트가 잘 작동하는지 어떻게 검사할 것인가?

-> 불리언 수식을 작성해서 프로그램이 자동으로 코드가 동작하는지에 대한 판단을 수행.

 

  • 판단 결과가 불리언 값. 참 : 모든테스트 통과 / 거짓 : 예상치 못한 일 발생.
  • 불리언 값은 컴퓨터에 의해 검증. 다양한 형태의 assert() 메서드를 호출하여 얻어냄.

단언은 구체적이어야 한다.

 

자바기반에서는 단언이 실패할 경우 출력될 정보를 단언에 추가해줄 필요가 있음.

 

픽스처

여러 테스트에서 공통으로 사용하는 객체들을 생성할때 어떻게 하면 좋을까?

-> 각 테스트 코드에 있는 지역변수를 인스턴스 변수로 바꾸고 setUp() 메서드를 재정의하여

이 메서드에서 인스턴스 변수들을 초기화하도록 함.

 

테스트 코드에서도 중복을 없애야 함.

객체를 원하는 상태로 세팅하는 동일한 경우의 코드가 여러군데 존재하는것은

(테스트 픽스처 fixture 정착물. 고정물)은 좋지 않음.

  • 복붙 반복 작성하는것은 시간이 소요되므로
  • 인터페이스를 수동으로 변경할 필요가 있는경우 여러 테스트를 고쳐야 함.

그러나 별도의 메서드로 분리한다면 그것이 호출되고 객체가 어떻게 초기화되었는지 인지해야 함.

 

외부 픽스처

픽스처 중 외부자원이 있을 경우 어떻게 해제할 것인가?

->tearDown() 메서드를 재정의하여 이곳에서 자원을 해제.

 

각 테스트는 그것이 실행되기 전과 실행된 후의 외부세계가 동일하게 유지되도록 해야함.

만약 테스트중에 파일을 열었다면 테스트가 끝나기 전에 이를 반드시 닫아야 함.

 

테스트 메서드

테스트 케이스 하나를 어떻게 표현할 것인가?

->'test'로 시작하는 이름의 메서드로 나타내면 됨.

 

객체지향 언어의 세가지 범주 구조계층 : 모듈(자바의 패키지) / 클래스 / 메서드

 

동일한 픽스처를 공유하는 모든 테스트는 동일한 클래스의 메서드로 작성될 것.

관습에 의해 메서드 이름은 'test'로 시작

 

- 메서드 이름은 모드는 사림이 보더라도 왜 작성되었는지 알수있도록 해야함.

- 테스트 메서드는 의미가 그대로 드러나는 코드로 읽기 쉽게.

 

예외 테스트

예외가 발생하는 것이 정상일 경우에 대한 테스트는 어떻게 작성할 것인가?

-> 예상되는 예외를 잡아서 무시하고, 예외가 발생하지 않는 경우에 한해서 테스트가 실패하게 만듬.

 

원하는 정확한 종류의 예외만을 잡아내야 함.

다른예외가 발생한 경우 테스트가 실패하도록.

 

전체테스트

모든 테스트를 한번에 실행하려면 어떻게 해야할까?

-> 모든 테스트 슈트에 대한 모음을 작성하면 됨. (각각의 패키지에 하나씩, 전체 패키지 테스트를 모아주는 테스트 슈트)

 


30장. 디자인 패턴

 

외부적 문제 해결 컨텍스트가 엄청나게 다양하더라도

공통의 해결책을 가진 공통의 문제를 발견할 것을 기대할 수 있다.

 

TDD에서는 설계를 디자인 패턴과는 조금 다른 관점으로 본다.

 

패턴 정의 테스트 작성 리팩토링
커맨드 계산 작업에 대한 호출invocation of a computation을 메시지가 아닌 객체로 표현 X  
값 객체 객체가 생성된 이후 그 값이 절대로 변하지 않게 하여 별칭 문제가 발생하지 않게 함 X  
널 객체 계산 작업의 기본 사례를 객체로 표현   X
템플릿 메서드

계산 작업의 변하지 않는 순서를 여러 추상 메서드로 표현.

이 추상 메서드들은 상속을 통해 특별한 작업을 수행하게끈 구체화

  X
플러거블 객체 둘 이상의 구현을 객체를 호출함으로써 다양성을 표현함.   X
플러거블 셀렉터 객체별로 서로 다른 메서드가 동적으로 호출되게 함. 필요없는 하위 클래스 생성을 피함   X
팩토리 메서드 생성자 대신 메서드를 호출함으로써 객체를 생성 X X
임포스터 현존하는 프로토콜을 갖는 다른 구현을 추가하여 시스템에 변이를 도입 X X
컴포지트 하나의 객체로 여러 객체의 행위 조합을 표현 X X
수집 매개변수 여러 다른 객체에서 계산한 결과를 모으기 위해 매개변수를 여러 곳으로 전달. X X

 

커맨드

간단한 메서드 호출보다 복잡한 형태의 계산 잡업에 대한 호출이 필요하다면 어떻게 해야할까?

-> 계산 작업에 대한 객체를 생성하여 이를 호출

 

메세지 하나를 보내는 것보다 호출이 조금 더 구체적이고 또 조작하기 쉬워지려면 객체가 해답.

호출 자체를 나타내기 위한 객체를 만드는 것.

 

객체를 생성할 때 계산에 필요한 모든 매개변수들을 초기화.

호출할 준비가 되면, run()과 같은 프로토콜을 이용해 계산을 호출

 

Runnable 
interface Runnable
    public abstract void run();

run()의 구현으로 원하는것 구현.

하지만 람다처럼 자주 쓰이지는 않음.

 

값 객체

널리 공유해야 하지만 동일성은 중요하지 않을때 객체를 어떤식으로 설계할 수 있을까?

-> 객체가 생성될때 객체의 상태를 설정한 수 이 상태가 절대 변할 수 없도록 함.

이 객체에 대해 수행되는 연산은 언제나 새로운 객체를 반환하게 만듬.

 

두 객체가 제삼의 다른 객체에 대한 참조를 공유할때 생기는 별칭문제

- 현재 의존하는 객체에 대한 참조를 외부에 알리지 않는것.

- 객체의 상태가 변하면 통지 받는 옵저버패턴

- 객체를 변하지 않게 함.

 

값 객체를 구현할 때 모든 오퍼레이션은 기존 객체는 변하지 않은 채로 두고, 새로운 객체를 반환해야 함.

 

교차하고 합하는 단위값, 대수학 같은 계산식 상황에서는 값객체를 사용하려는 경향이 있음.

 

값객체가 코드읽기 디버깅하기 쉽기 때문.

 

모든 값 객체는 동등성을 구현해야 함. 

 

널객체

객체의 특별한 상황을 표현하고자 할때 어떻게 해야할까?

-> 그 특별한 상황을 표현하는 새로운 객체를 만들자. 그리고 객체에 다른 정상적인 상황을 나타내는 객체와 동일한 프로토콜을 제공.

 

절대로 null을 반환하지 않는 메소드

public boolean setReadOnly() {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkWrite(path);
    }
    return fs.setReadOnly(this);
}
// null검사대신 새로운 클래스를 만듬.
LaxSecurity
public void canWrite(String path){
}

SecurityManager
public static SecurityManager getSecurityManager(){
    return security == null ? new LaxSecurity() : security;
}

File
public boolean setReadOnly() {
    SecurityManager security = System.getSecurityManager();
    security.canWrite(path);
    return fileSystem.setReadOnly(this);
}

 

템플릿 메서드

작업 순서는 변하지 않지만 각 작업 단위에 대한 미래의 개선 가능성을 열어두고 싶은 경우 어떻게 표현할 것인가?

-> 다른 메서드들을 호출하는 내용으로만 이루어진 메서드를 만든다.

 

상위클래스에는 다른 메서드를 호출하는 내용으로만 이루어진 메서드를 만들고,

하위클래스에서는 이 각각의 메서드를 서로 다른 방식으로 구현.

 

TestCase
public void runBare() throws Throwable {
    setUp();
    try{
        runTest();
    }
    finally {
        tearDown();
    }
}

 

하위 클래스는 그들이 원하는대로 setUp(), runTest(), tearDown() 을 구현

 

플러거블 객체

변이를 어떻게 표현할 것인가?

-> 간단한 방법은 명시적인 조건문을 사용하는 것.

 

조건문을 두번째로 볼떄 객체설계시 기초인 블러거블 객체가 나올때.

 

명시적인 인터페이스를 사용하는 언어에서는 두 플러거블 객체가 동일한 인터페이스를 구현하게 함.

 

플러거블 셀렉터

인스턴스별로 서로 다른 메서드가 동적으로 호출되게 하려면 어떻게 해야할까?

->메서드의 이름을 저장하고 있다가 그 이름에 해당하는 메서드를 동적으로 호출.

 

단지 메서드 하나만 구현하는 하위클래스가 열개있다면?

상속은 작은 변이를 다루기엔 무거운기법이다

-> switch문을 같는 하나의 클래스를 만들자.

 

확실히 직관적인 상황에서 코드를 정리하기 위한 용도로만 플러거블 셀렉터를 사용하자.

 

팩토리 메서드

새 객체를 만들때 유연성을 원하는 경우 객체를 어떻게 생성하는가?

-> 생성자를 쓰는 대신 일반 메서드에서 객체를 생성.

 

변경하지 않고 다른 클래스의 인스턴스를 반환할 수 있는 유연함.

 

유연함이 필요할 떄만 사용, 기본의 생성자를 쓰는것으로 충분하기 때문.

 

사칭 사기꾼 imposter

기존의 코드에 새로운 변이를 도입하려면 어떻게 해야할까?

-> 기존의 객체와 같은 프로토콜을 갖지만 구현은 다른 새로운 객체를 추가

 

imposter : 남의 이름을 사칭하는 자.

 

절차적 프로그래밍에서 변이를 도입하려면 조건문을 추가해야하므로, 다형성 메시지 구조로 추가.

 

리팩토링 중 나타나는 imposter 의 두가지 예

  • 널 객체 - 데이터가 없는 상태를 데이터가 있는 상태와 동일하게 취급
  • 컴포지트 - 객체의 집합을 단일 객체처럼 취급할 수 있다.

imposter 널 객체 는 TestDouble로도 불리움

여러 방식의 가짜 객체를 사용하는 방식을 imposter라고 묶어 부르는것 같다.

(source https://stackoverflow.com/questions/22709449/what-is-imposter-design-pattern)

 

 

컴포지트

하나의 객체가 다른 객체 목록의 행위를 조합한 것처럼 행동하게 만들려면 어떻게 해야할까?

-> 객체 집합을 나타내는 객체를 단일 객체에 대한 imposter로 구현.

 

켄트벡이 TDD하면서 자주 즐겨쓰는 패턴인듯.

 

리팩토링시 중복이 나타나는 순간 컴포지트 패턴을 도입.

 

수집 매개변수

여러 객체에 걸쳐 존재하는 오퍼레이션의 결과를 수집하려면 어떻게 해야할까?

-> 결과가 수집될 객체를 각 오퍼레이션의 매개변수로 추가.

 

수집 매개 변수를 추가하는 것은 컴포지트의 일반적인 귀결이다.

 

싱글톤

전역변수를 제공하지 않는 언어에서 전역변수를 사용하려면 어떻게 해야할까?

-> 사용하지 마라. (단호)

 


31장. 리팩토링

 

시스템의 설계를 작은 단계를 통해 변화시키는 방법 소개

일반적으로 리팩토링은 프로그램의 의미를 변경시키면 안됨.

하지만 TDD 에서는 테스트가 통과했는지가 중요하며, 상수가 변수로 바꾸는 행위도 수행됨.

통과하는 테스트 집합에 변화를 주지 않으면 되는것.

 

차이점 일치시키기

비슷해 보이는 두 코드조각을 합치려면 어떻게 해야 할까?

-> 두 코드가 단계적으로 닮아가게끔 수정 후 이 둘이 완전 동일해지면 둘을 합침.

 

단계를 크게 건너뛰는 리팩토링을 줄여야 함.

 

  • 두 반복문의 구조가 비슷하다. -> 이 둘을 동일하게 만들고나서 하나로 합침.
  • 조건문에 의해 나눠지는 두 분기의 코드가 비슷하다 -> 이 둘을 동일하게 만들고 조건문을 제거.
  • 두 클래스가 비슷하다. -> 둘을 동일하게 만들고 하나를 제거.

 

변화 격리하기

객체나 메서드의 일부만 바꾸려면 어떻게 해야할까?

-> 일단 바꿔야 할 부분을 격리.

 

일단 바꿀부분을 격리하고 작업하면 나중에 되돌리기도 수월해짐.

 

변화를 격리하기 위해 사용할 수 있는 몇가지 방법

- 메서드 추출하기, 객체 추출하기, 메서드 객체 Method Object

 

데이터 이주시키기

표현양식을 변경하려면 어떻게 해야할까?

-> 일시적으로 데이터를 중복시킴.

 

내부 표현양식을 변경한 후 외부 인터페이스를 변화시키는 방법.

  • 새로운 포맷의 인스턴스 변수를 추가
  • 기존 포맷의 인스턴스 변수를 세팅하는 모든 부분에서 새로운 인스턴스 변수도 세팅하게 만듦.
  • 기존 변수를 사용하는 모든 곳에서 새 변수를 사용하게 만듦.
  • 기존 포맷을 제거
  • 새 포맷에 맞게 외부 인터페이스를 변경.

외부 API 를 먼저 변화시키기를 원할때

  • 새 포맷으로 인자를 하나 추가
  • 새 포맷인자에서 이전 포맷의 내부적 표현양식으로 번역
  • 이전 포맷 인자를 삭제
  • 이전 포맷을 사용하는 것들을 새 포맷으로 바꿈.
  • 이전 포맷을 지움.

단계적 이주를 수행함.

 

메서드 추출하기

길고 복잡한 메서드를 읽기 쉽게 만들려면 어떻게 할까?

-> 긴 메서드의 일부분을 별도의 메서드로 분리해내고 이를 호출하게 함.

 

1. 기존의 메서드에서 별도의 메서드로 분리할 수 있을만한 부분을 찾아냄. 반복문 내부의 코드나 반복문 전체, 혹은 조건문의 가지들.

2. 추출할 영역의 외부에서 선언된 임시변수에 대해 할당하는 문장이 없는지 확인.

3. 추출할 코드를 복사해서 새 코드를 붙임.

4. 원래 메서드에 있던 각각의 임시변수와 매개변수 중 새 메서드에서도 쓰이는게 있으면, 

   이들을 새 메서드의 매개변수로 추가.

5. 기존의 메서드에서 새 메서드를 호출.

 

 

 

메서드 인라인

너무 꼬여있거나 산재한 제어 흐름을 단순화하려면 어떻게 할까?

-> 메서드를 호출하는 부분을 호출될 메서드의 본문으로 교체

 

1. 메서드를 복사

2. 메서드 호출하는 부분을 지우고 복사한 코드를 붙임.

3. 모든 형식 매개변수를 실제 매개변수로 변경.

 

여러 추상화 계층을 인라인시켜놓고 흐름을 파악한다음, 예상이 아닌 실제적인 필요성에 의한 추상화를 진행.

 

인터페이스 추출하기

자바 오퍼레이션에 대한 두번째 구현을 추가하려면 어떻게 해야할까?

-> 공통되는 오퍼레이션을 담고 있는 인터페이스를 만들면 됨.

 

1. 인터페이스를 선언.

2. 기존 클래스가 인터페이스를 구현하도록 만듦.

3. 필요한 메서드를 인터페이스에 추가. 필요하다면 클래스에 존재하는 메서드들의 가시성을 높여줌.

4. 가능한 모든 곳의 타입 선언부에서 클래스 이름 대신 인터페이스 이름을 사용하게 바꿈.

 

인터페이스가 도입될 필요가 있을때 때때로 모의객체나 크래시 테스트 더미를 도입하기도 함.

 

메서드 옮기기

메서드를 원해 있어야 할 장소로 옮기려면 어떻게 해야할까?

-> 어울리는 클래스에 메서드를 추가해주고 , 그것을 호출하게 함.

 

1. 메서드를 복사

2. 원하는 클래스에 붙이고 이름을 적절히 지어준 후 컴파일

3. 원래 객체가 메서드 내부에서 참조됨다면, 원래 객체를 새 메서드의 매개 변수로 추가.

   원래 객체의 필드들이 참조되고 있다면 그것들도 매개 변수로 추가.

   만약 원래 객체의 필드들이 갱신된다면 포기.

4. 원래 메서드의 본체를 지우고 , 그곳에 새 메서드를 호출하는 코드를 넣음.

 

메서드 옮기기의 훌륭한 세 가지 속성.

  • 코드에 대한 깊은 이해가 없더라도 언제 이 리팩토링이 필요한지 알아낼 수있음. 다른 객체에 대한 두개 이상의 메시지를 보내는 코드를 볼 때마다 메서드 옮기기를 해주면 됨.
  • 리팩토링 절차가 빠르고 안전.
  • 리팩토링 결과가 종종 새로운 사실을 알려줌.

 

메서드 객체

여러개의 매개변수와 지역변수를 갖는 복잡한 메서드를 어떻게 표현할까?

-> 메서드를 꺼내서 객체로 만듦.

 

1. 메서드와 같은 매개 변수를 갖는 객체를 만듬.

2. 메서드의 지역 변수를 객체의 인스턴스 변수로 만듦.

3. 원래 메서드와 동일한 내용을 갖는 run() 이라는 이름의 메서드를 만듦.

4. 원래 메서드에서는 새로 만들어진 클래스의 인스턴스를 생성하고 run()을 호출.

 

- 매서드 객체는 시스템에 완전히 새로운 로직을 추가할때 유용함.

그것만을 위한 작은 규모의 테스트와 함께. 

 

- 메서드 추출하기를 적용할 수 없는 코드를 간결하게 만들기 위한 용도로 적합.

 

매개변수 추가

메서드에 매개변수를 추가하려면?

 

1. 메서드가 인터페이스에 선언되어 있다면 일단 인터페이스에 매개변수를 추가.

2. 매개변수를 추가

3. 컴파일 에러가 여러분에게 어딜 고쳐야 하는지 알려줄 것.

 

매개변수를 추가하는것은 일종의 확장단계.

또한 하나의 데이터 표현을 다른 표현으로 변경하는 작업의 일부.

 

일단 매개변수를 추가하고 기존 매개 변수 사용하는 부분을 삭제한후 기존 매개변수 제거.

 

메서드 매개변수를 생성자 매개변수로 바꾸기

하나 이상의 메서드의 매개변수를 생성자로 옮기려면?

 

1. 생성자에 매개변수를 추가

2. 매개변수와 같은 이름을 갖는 인스턴스 변수를 추가.

3. 생성자에서 인스턴스 변수의 값을 설정.

4. 'parameter' 를 'this.parameter' 로 하나씩 찾아 바꿈.

5. 매개 변수에 대한 참조가 더 이상 존재하지 않으면 해당 매개변수를 메서드와 모든 호출자에서 제거.

6. 이제 필요 없어진 'this.'을 제거

7. 변수명을 적절히 변경.

 

동일한 매개변수를 같은 객체의 서로 다른 몇몇 메서드로 전달하는경우,

매개변수를 한번만 전달하게끔 단순화.

 

'IT Book Summary > TDD' 카테고리의 다른 글

32장. TDD 마스터 하기  (0) 2020.04.18
3부 테스트 주도 개발의 패턴. 25장- 28장  (0) 2020.04.07
xUnit 21장 - 24장  (0) 2020.03.31
18장-20장 xUnit  (0) 2020.03.24
13-17장  (0) 2020.03.18