ABOUT ME

인우 블로그입니다.

Today
Yesterday
Total
  • TDD[3]
    Spring 2021. 10. 8. 15:45

    테스트 주도 개발의 패턴


    테스트 주도 개발 패턴

    1. 테스트 한다는건 무엇을 뜻하는지?
    2. 언제 테스트를 해야 하는지?
    3. 테스트할 로직을 어떻게 고를지?
    4. 테스트할 데이터를 어떻게 고를지?

    테스트란?

    테스트할 시간이 어딨어. 그냥 릴리즈해 이 장면에 대해서는 결과를 보장할 수 없다. 스트레스를 점점 많이 받으면 결국 실패하게 된다.
    - 스트레스를 받으면 테스트를 실행한다. 테스트를 실행하면 스트레스를 받는다.
    - 이 부분의 경우 자동화된 테스트가 존재하다면 두려운 정도를 선택할 수 있다. (스트레스 또한 줄일 수 있게 된다.)

    격리된 테스트

    매일 아침 출근하면 지난밤 테스트 결과를 담은 종이더미가 놓여 있었다. 이 경험에서 두 가지의 교훈을 얻을 수 있었다.

    1. 테스트가 충분히 빨라서 내가 직접, 자주 실행할 수 있게끔 만들자.
      • 내가 만든 에러를 누구보다 빠르게 잡을 수 있음.
    2. 많은 양의 실패 테스트가 반드 시 양의 문제가 아닐 수 도있다.
      • 앞 부분에서 실행된 테스트가 실패한 후 그 영향으로 다음 테스트부터는 시스템이 예측 불가능한 상태에 놓이는 경우가 허다하다.
        • 그렇기에 테스트는 작은 스케일로 진행하며, 각각의 테스트와 완전히 독립적이어야 한다.

    테스트 목록

    해야하는 목록이 많아질 수록 내가 하던 일에 대한 집중력이 떨어진다. 집중력이 떨어지면 할일이 많아진다.

    • 머릿속에 있는 목록에서 임의의 항목을 무시하고 변덕스럽게 프로그래밍을 할 경우 이 악순환에서 벗어날 수 없게 된다.

    테스트 우선

    테스트는 대상이 되는 코드를 작성하기 직전에 작성하는 것이 좋다. 스트레스 - 테스트 에서도 테스트가 먼저가 된다면 스트레스가 줄어 테스트도 줄게된다.

    단언 우선

    테스트를 작성할 때 단언은 가장 먼저 사용해라.
    단언을 먼저 사용할 경우 작업을 단순하게 만드는 강력한 효과를 볼 수 있다. 구현에 대해 전혀 고려하지 않고 테스트만 작성할 때도 몇 가지 문제들을 한번에 해결하는 것이다.

    1. 시스템을 개발할 때 무슨 일부터 하는가? 완료된 시스템은 이럴 것 이다! 라는 이야기 부터 작성한다.
    2. 특정 기는을 개발할 때 무슨 일부터 하는가? 기능이 완료되면 통과할 수 있는 테스트 부터 작성한다.
    3. 테스트를 개발할 때 무슨 일부터 하는가? 완료될 때 통과해야 할 단 언부터 작성한다.

    테스트 데이터

    1. 테스트를 읽을 때 쉽고 따라갈 수 있을만한 좋은 데이터를 사용해라 테스트 작성에도 청중이 존재한다.
    2. 단지 데이터 값을 산발하기 위해 데이터 값을 산발하지 마라.
    3. 데이터 간에 차이가 있다면 그 속에 어떤 의미가 있어야한다.
    4. 만약 1과 2사이에 어떠한 개념적 차이도 없다면 1을 사용해라.
    5. 여러 입력을 다루어야 한다면 테스트 또한 여러 입력을 반영해라.
    6. 테스트 데이터에 대한 대안은 실제 세상에서 얻어진 실제 데이터를 사용하는 것이다.
      • 실제 실행을 통해 수집한 외부 이벤트의 결과를 이용하여 실시간 시스템을 테스트하고자 하는 경우.
      • 예전 시스템의 출력과 현재 시스템의 출력을 비교하는 경우(병렬)
      • 시뮬레이션 시스템을 리팩토링한 후 기존과 정확히 동일한 결과가 나오는지 확인하고자 하는 경우

    명백한 데이터

    테스트를 작성할 때는 컴퓨터뿐 아니라 후에 코드를 읽을 다른 사람들도 생각해야 한다.
    - 추후 자신의 코드를 보며 무슨 생각으로 이 코드를 짯는지 의문이 들 수 있다.

    빨간 막대 패턴

    이 패턴들은

    • 테스트를 언제, 어디에 작성할 것인지?
    • 테스트 작성을 언제 멈출 출지에 대한 것이다.

    한 단계 테스트

    하향식, 상향식 둘 다 모두 TDD의 프로세스를 효과적으로 설명할 수 없다. 첫째로 이와 같은 수직적 메타포는 프로그램이 시간에 따라 어떻게 변해 가는지에 대한 단순화된 시각일 뿐이다.
    이보다 성장이란 단어를 보면 일종의 자기 유사성을 가진 피드백 고리를 암시하는데, 이 피드백 고리에서 환경이 프로그램에 영향을 주고 프로그램이 다시 환경에 영향을 준다.
    둘째로 만약 메타포가 어떤 방향성을 가질 필요가 있다면 (상향 혹은 하향보다는) 아는 것에서 모르는 것으로 라는 방향이 유용할 것이다.

    시작 테스트

    어떤 테스트부터 시작해야 좋을지? 첫 걸음으로 현실적인 테스트를 하나 작성한다면 상당히 많은 문제를 한번에 해결해야 하는 상황이 될 것이다.
    빨/초/리팩, 빨/초/리팩 주기가 짧은걸 원할 수록 정말 발견하기 쉬운 입력과 출력을 사용하여 이 테스트 시간을 짧게 줄일 수 있다.
    한 단게 테스트는 시작 테스트에도 적용이 되며, 당신에게 뭔가를 가르켜 줄 수 있으면서도 빠르게 구현할 수 있는 테스트를 선택해라

    설명 테스트

    자동화된 테스트가 널리 쓰이게 하려면 테스트를 통해 설명을 요청하고 테스트를 통해 설명해야 한다.
    더 넘어가 추후에는 시퀀스 다이어그램에 나타난 모든 요소들을 포함하는 테스트 케이스를 작성해 볼 수 있다.

    학습 테스트

    새로운 클래스, 새로운 메서드를 곧 바로 사용하는 것 보다는 API가 우리 예상대로 흘러가는지 확인하는 작은 테스트를 만들어서 테스트 할 수 있다.

    또 다른 테스트

    새 아이디어가 떠오르면 존중하고 맞이하되 그것이 내 주의를 흩뜨리지 않게 한다. 그 아이디어를 리스트에 적어놓고는 하던 일로 다시 돌아간다.

    회귀 테스트

    회귀 테스트란 사실 완벽한 선견지명이 있다면, 처음 코딩할 때 작성해야 하는 테스트 이다.
    전체 애플리케이션 차원에서 테스트를 수행하는 것에서도 가치를 얻을 수 있다. 애플리케이션 차원의 회귀 테스트는 시스템의 사용자들이 여러분에게 정확히 무엇을 기대했으며 무엇이 잘못되었는지 말할 기회를 준다.
    좀더 작은 차원에서 회귀 테스트는 당신의 테스트를 개선하는 방법이 된다.

    휴식

    지치고 고난에 빠질 때에는 휴식이다.
    피로가 쌓이면 판단력이 흐려진다.
    판단력이 흐려지면 피로가 쌓인다.

    다시 하기

    길을 잃었을 때에는 모든 코드를 다 지워버리고 처음부터 다시 해보자. 다시

    싸구려 책상, 좋은 의자

    책상보다는 진짜 진짜 좋은 의자를 사용하자...

    테스팅 패턴

    더 상세한 테스트 작성법

    자식 테스트

    지나치게 큰 테스트는 원래 테스트 케이스의 깨지는 부분에 해당하는 작은 테스트 케이스를 작성하고 그 작은 테스트 케이스가 실행되도록 하라. 그 후에 다시 원래의 큰 테스트 케이스를 추가하라.

    모의 객체

    비용이 많이 들거나 복잡한 리소스에 의존하는 객체를 테스트하려면 상수를 반환 하거나 속임수 버전의 리소스를 만들면 된다.

    • 테스트에서는 대표적으로 Mock 객체가 있다.

    셀프 션트 자기가 보낸 것이 다시 자기에게 제대로 돌아오는지 확인하는 루프백 테스트와 유사한 단어

    셀프 션트 패턴은 테스트 케이스가 구현할 인터페이스를 얻기 위해 인터페이스 추출을 해야 한다. 인터페이스를 추뤃나는 것이 더 쉬운지, 존재하는 클래스를 블랙 박스로 테스트하는 것이 더 쉬운지는 개발자가 결정해야 한다.

    로그 문자열

    메세지의 호출 순서가 올바른지 검사하기 위하여 사용되며, 메세지 호출 시 그 문자열에 추가되도록 한다.

    • 옵저버를 구현하고 이벤트 통보가 원하는 순서대로 발생하는지를 확인하고자 할 때 유용하다.
    • 순서가 상관 없는 경우 문자열 집합을 저장하고 있다가 단언에서 집합 비교를 수행하면 된다.

    크래시 테스트 더미

    호출되면 에러가 발생할거 같은 코드는 호출하는 대신 그냥 예외를 발생시키기만 하는 특수한 객체를 만들어서 이를 호출한다.

    깨진 테스트

    혼자서 프로그래밍을 한다면 프로그래밍 세션을 마지막 테스트가 깨진 상태로 끝마치는 것이 좋다.

    • 다음에 프로그래밍을 할 때 반 쪽짜리 문장을 보게 된다면 내가 그 문장을 작성할 때 무슨 생각을 했는지 떠올리게 된다. 사람마다 다른거 아닌가?

    깨끗한 체크인

    팀 프로그래밍을 할 때에는 프로그래밍 세션을 끝마치는게 좋다.

    • 자신이 프로그래밍을 시작할 때 자신 이후에 어떤 일이 발생했는지 알 수 없다. 그렇기에 항상 모든 테스트가 돌아가는 상태로 만들어 두어야 한다.

    초록 막대 패턴

    깨진 테스트가 존재하다면 그걸 고쳐야 한다. 빨간 막대를 가능한 빨리 고쳐야 하는 조건으로 다룬다면 금새 초록 막대로 옮겨갈 수 있다는 것을 깨닫게 될 것이다.

    가짜로 구현하기 (진짜로 만들기 전 까지만)

    실패하는 테스트를 제작 후 첫 번째 구현은 상수를 반화나게 하라. 그 이후 단계적으로 상수를 변수로 사용한는 수식으로 변경한다.
    가짜로 구현하기를 강력하게 만드는 두 가지 효과

    • 심리학적 : 초록막대에 있는 것은 빨간 막대 상태에 있는 것과 천지 차이이며, 막대가 초록색일 경우 자기가 어디에 서 있는지 알고 있으며, 확신을 가지고 리팩토링해 갈 수 있다.
    • 범위조절 : 구체적인 예에서 시작해서, 일반화하게 되면, 쓰잘데기 없는 고민으로 때 이르게 혼동하는걸 예방할 수 있으며, 다음 테스트 케이스 구현 시 이전 테스트의 작동이 보장된다는 것을 알기에 그 다음 테스트 케이스에 집중할 수 있다.

    삼각측량

    추상화 과정을 테스트 주도할 때 오로지 예가 두 개 이상일 경우에만 추상화를 하라.
    추상화를 올바르게 할지 감잡기 어려울 때만 삼각측량을 사용하고 그 외의 경우 명백한 구현이나 가짜로 구현하기에 의존한다.

    명백한 구현

    가짜로 구현하기와 삼각측량의 어중간한 성질에 특별한 미덕이 있는건 아니다. 뭘 타이핑해야 할고, 그걸 할 수 있다면 재빠르게 해라

    하나에서 여럿으로

    객체 컬렉션을 다루는 연산의 경우 컬렉션 없이 구현하고 그 다음에 컬렉션을 사용한다.

    xUnit 패턴

    xUnit 계열 테스팅 프레임워크를 위한 패턴

    단언

    테스트를 자동화하려면 결과를 평가하는 데 개입되는 인간의 판단을 모조리 끄집어 내야 한다.
    버튼을 누르면 코드의 작동이 올바른지 검증하는 데 필요한 모든 판단은 컴퓨터가 되어야 한다. 이 것은 다음 사항을 의미한다.

    • 판단 결과가 불리언 값이어야 한다. 일반적으로 참 값은 모든 테스트가 통과했음을 의미하고, 거짓 값은 뭔가 예상치 못했던 일이 발생했음을 의미한다.
    • 이 불리언 값은 컴퓨터에 의해 검증되어야 한다. 보통 다양한 형태의 assert() 메서드를 호출하여 이 값을 얻어낸다.

    픽스처

    여러 테스트에서 공통적으로 사용하는 객체들을 생성할 때에는 테스트 코드에 있는 지역 변수를 인스턴스 변수로 바꾸고 setUp() 메서드를 재정의하여 이 메서드에서 인스턴스 변수들을 초기화 하도록 한다.
    이와 같은 중복은 다음과 같은 장, 단점이 있다.

    1. 장점
      • 복사해서 붙이는 반복 작성 시간을 줄일 수 있음
      • 인터페이스를 수동으로 변경할 필요가 있을 경우 여러 테스트를 고쳐주어야 함
    2. 단점
      • 별도의 메서드로 분리하여 나머지 테스트를 작성하기 전 혹은 도중에 그 메서드가 호출된다는 점과 객체들이 어떻게 초기화 되는지 기억해야 한다.

    외부 픽스처

    각 테스트의 목적 중 하나는 테스트가 실행되기 전, 후의 외부 세계가 동일하게 유지되도록 만드는 것이다.
    픽스처와 동일하게 tearDown() 메서드를 정의 후 이곳에서 자원을 해제하면 된다.

    테스트 메서드

    앞으로의 수백 수천개에 해당하는 테스트를 관리하는 방법으로 객체지향 프로그래밍 언어는 세 가지 범주의 구조 계층을 제공한다.
    만약 테스트를 일반적인 소스코드처럼 작성하길 원한다면, 테스트를 위의 구조에 끼워 맞출 방법을 찾아야 한다.

    1. 모듈(자바의 패키지)
    2. 클래스
    3. 메서드
      1. 픽스처를 사용하기 위해 클래스를 사용하기로 결정했다면 테스트는 동일한 픽스처를 공유하는 클래스의 메서드로 작성될 것이다.
      2. 테스트 메서드는 의미가 그대로 드러나는 코드로, 읽기 쉬워야 한다.

    예외 테스트

    예외 테스트의 정상은 예외를 잡아서 해당 예외가 발생할 경우 통과하는 테스트를 만드는 것이다.
    예외가 제대로 발생하는지에 대한 테스트는 해당 코드 마지막 부분에 fail() 메서드를 호출하여 테스트가 실패했음을 알려줄 수 있다.

    전체 테스트

    모든 테스트를 한번에 실행하려면 모든 테스트 슈트에 대한 모음을 작성하면 된다.

    디자인 패턴

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

    1. 커맨드 - 계산 작업에 대한 호출을 메세지가 아닌 객체로 표현한다.
    2. 값 객체 - 객체가 생성된 이후 그 값이 절대로 변하지 않게 하여 별칭 문제가 발생하지 않게한다.
    3. 널 객체 - 계산 작업의 기본 사례를 객체로 표현한다.
    4. 템플릿 메서드 - 계산 작업의 변하지 않는 순서를 여러 추상 메서드로 표현한다. 이 추상 메서드들은 상속을 통해 특별한 작업을 수행하게끔 구체화 된다.
    5. 플러거블 객체 - 둘 이상의 구현을 객체를 호출함으로써 다양성을 표현한다.
    6. 플러거블 셀렉터 - 객체별로 서로 다른 메서드가 동적으로 호출되게 함으로써 필요 없는 하위 클래스의 생성을 피한다.
    7. 팩토리 메서드 - 생성자 대신 메서드를 호출함으로써 객체를 생성한다.
    8. 임포스터 - 현존하는 프로토콜을 갖는 다른 구현을 추가하여 시스템에 변이를 도입한다.
    9. 컴포지트 - 하나의 객체로 여러 객체의 행위 조합을 표현한다.
    10. 수집 매개 변수 - 여러 다른 객체에서 계산한 결과를 모으기 위해 매개 변수를 여러 곳으로 전달한다.

    커맨드

    간단한 메서드 호출보다 복잡한 형태의 계산 작업에 대한 호출이 필요하다면 계산 작업에 대한 객체를 생성하여 이를 호출하면 된다.
    메세지 보내기는 참 좋은 기능이며, 프로그래밍 언어는 메세지를 쉽게 전송할 수 있다. 하지만 단순히 메시지 보내는 것만으로 충분하지 않을때도 존재한다.

    • 메세지 보내기란 메서드 호출의 의미와 비슷하다.
      이 경우 메세지 하나를 보내는 것보다 호출이 조금 더 구체적이고 또 조작하기 쉬워지려면, 바로 객체가 해답이 된다.

    값 객체

    공유해야 하지만 돌일성은 중요하지 않을 때 객체가 생성 될 경우 객체의 상태를 설정한 후 이 상태가 절대 변할 수 없도록 한다. 그리고 이 객체에 대해 수행되는 연산은 언제나 새로운 객체를 반환하게 만든다.

    널 객체

    객체의 특별한 상황을 표현하고자 할 경우 새로운 객체를 만들면 되며, 해당 객체에 다른 정상적인 상황을 나타내는 객체와 동일한 프로토콜을 제공하면 된다.
    member.orElse(new Member()) 같이 null 객체를 전달하기 보다는 새로운 객체를 전달

    • 프로토콜이란 인터페이스와 비슷한 뜻이다.

    템플릿 메서드

    작업순서는 변하지 않지만 각 작업 단위에 대한 미래의 개선 가능성을 열어두고 싶은 경우 다른 메서드들을 호출하는 내용으로만 이루어지는 메서드를 만들면 된다.
    프로그래밍이란 고전적인 순서들로 가득하다.

    1. 입력/처리/출력
    2. 메세지 보내기/응답 받기
    3. 명령 읽기/결과 내보내기

    이러한 순서들을 범용성에 대해서 명백하게 나타내는 동시에 각 단계의 구현에 대해서는 변화를 주고 싶을 때 사용한다.

    플러거블 객체

    TDD 의 두 번째 수칙은 중복을 제거하는 것이기에 명시적 조건문이 전염되는 싹을 애초에 잘라야 한다. 조건문을 두 번째로 볼때가 바로 객체 설계시의 가장 기초인 플러거블 객체를 끄집어낼 때이다.
    지저분한 중복 조건문들 이 경우에 대한 해법은 플러거블 객체를 만드는 것이며, 명시적인 인터페이스를 사용하는 언어에서는 두 플러거블 객체가 동일하면 인터페이스를 구현하게 해야 한다.

    플러거블 셀렉터

    인스턴스별로 서로 다른 메서드가 동적으로 호출되게 하려면 메서드 이름을 저장하고 있다가 해당하는 메서드를 동적으로 호출해야 한다.
    - 단지 메서드 하나만 구현하기 위해 수 많은 하위 클래스가 있는건 너무 무거운 기법이다.
    플러거블 셀렉터 해법은 리플렉션을 이용하여 동적으로 메서드를 호출하는 것이다.
    - 단 메서드 호출 여부를 확인하기 위해 코드를 추적하는 용도로 사용하는 것은 과용이 될 수 있다. 직관적인 상황에서 코드 정리용으로만 사용하자.

    팩토리 메서드

    새 객체를 만들 때 유연성을 원하는 경우에 사용한다. 그렇지 않는다면 무조건 생성자를 이용하자

    사칭 사기꾼

    기존 코드에서 새로운 변이를 도입하려면 기존 객체와 같은 프로토콜을 가지지만 구현은 다른 새로운 객체를 추가한다. 그러나 이러한 변이를 도입 시 여러 메서드를 수정하는 경우가 존재하는데

    1. 널 객체 : 데이터 없는 상태를 데이터가 있는 상태랑 동일하게 취급
    2. 컴포지트 : 객체의 집합을 단일 객체처럼 취급

    컴포지트

    하나의 객체가 다른 객체 목록의 행위를 조합한 것처럼 행동하게 만드려면 객체 집합을 나타내는 객체를 단일 객체에 대해 임포스터로 구현한다.
    컴포지트 패턴을 적용하는 것은 프로그래머의 트릭이지 세상 사람들에게 일반적으로 받들여 지는 것이 아니다. 리팩토링에 잠처 익숙해지고 있으므로,
    중복이 나타나는 순간에 컴포지트를 도입해보고 이로 인하여 프로그램의 복잡함이 사라지는 지 확인해볼 수 있다는 점이다.

    수집 매개 변수

    여러 객체에 걸쳐 존재하는 오퍼레이션의 결과를 수집하려면 수집될 각 객체를 각 오퍼레이션의 매개 변수로 추가하면 된다.
    - 수집 매개 변수를 추가하는 것은 컴포지트의 일반적인 귀결이다.

    싱글톤

    전역변수를 지원아한다면 사용하지마라.

    리팩토링

    TDD 에서 리팩토링은 조금 특이한 방법으로 사용된다. 일반적인 리팩토링은 프로그램의 의미론을 변경해서는 안된다. 하지만 TDD 에서 우리가 신경 쓰는 부분은 현재 이미 통과한 테스트들 뿐이다.
    - 상수를 변수로 바꿔도 리팩토링이다. TDD 에서는 특정 행위가 통과하는 테스트의 집합에 아무 변화를 주지 않기에 해당 행위도 리팩토링으로 간주한다.

    차이점 일치시키기

    비슷해 보이는 두 조각 코드를 합치려면 단계적으로 닮아가게끔 수정한 후 두 코드가 완전히 동일해지면 둘을 합치면 된다.
    리팩토링은 신경을 고문하는거와 비슷한데 특히 추론 과정이 길어지면 지금 고치려고 하는 부분이 결과에 영향을 주지 않을거라고 믿게 된다.

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

    변화 격리하기

    객체나 메서드의 일부분을 변경하라면 먼저 바꿔야 할 부분을 격리한다. 일단 변경할 부분을 격리하고 나서 바꾸는 작업을 수행하면 작업을 되돌리기도 매우 수월할거라고 알게 될 거다.
    변화를 격리하기 위해 사용되는 방법으로는 메서드 추출하기, 객체 추출하기, 메서드 객체 등이 있다.

    데이터 이주시키기

    표현 양식을 변경하려면 일시적으로 데이터를 중복시키면 된다.

    • 내부에서 외부로의 변화란 내부의 표현 양식을 변경한 후 외부 인터페이스를 변화시키는 방법을 말한다.
      1. 새로운 포맷의 인스턴스 변수를 추가한다.
      2. 기존 포맷의 인스턴스 변수를 세팅하는 모든 부분에서 새로운 인스턴스 변수도 세팅하게 만든다.
      3. 기존 변수를 사용하는 모든 곳에서 새 변수를 사용하게 만든다.
      4. 기존 포맷을 제거한다.
      5. 새 포맷에 맞게 외부 인터페이스를 변경한다.
      • 때로는 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. 변수명을 적절히 변경한다.

    TDD 마스터하기

    통합질문

    단계는 얼마나 커야하나?

    1. 각 테스트가 다뤄야 할 범위는 얼마나 넓은가?
    2. 리팩토링을 하면서 얼마나 많은 중간 단계를 거쳐야 하는가?

    크고 작은 테스트 및 리팩토링을 만들 수 있겠지만 한쪽만 할줄 아는게 아닌 모두 할줄 알아야 한다. 시간이 지남에 따라 테스트 주도 개발자의 경향은 분명히 나타나게 된다. 단계는 점점 작아지는 것이다.
    자동화된 리팩토링 툴은 리팩토링을 가속화 시킨다. 좋은 툴의 지원이 있다는걸 알면 코드가 어떤 구조를 갖추길 원하는지 보기 위하여 여러 가지 실험을 시도하며, 리팩토링에 더 적극적이게 될 것이다.

    테스트할 필요가 없는 것은 무엇인가?

    두려움이 지루함으로 변할 때 까지 테스트를 작성해라. 조건문, 반복문, 연산자, 다형성 은 무조건 테스트해야 한다.
    남이 만든 테스트는 불신하지 않는이상 테스트를 작성하지 마라.

    좋은 테스트를 갖췄는지의 여부를 어떻게 알 수 있는가?

    설계 문제가 있음을 알려주는 테스트 속성

    1. 긴 셋업 코드
    2. 셋업 중복
    3. 실행 시간이 오래 걸리는 테스트
      • 방치될 수 있으며, 방치로 인하여 실패하는 테스트 일 수 있다.
      • 테스트 실행 시간이 달다는 것은 애플리케이션의 작은 부분만 따로 테스트하기가 힘들다는 것을 의미한다. 작은 부분만 테스트할 수 없다는 것은 설계 문제를 의미하고 설계를 적절히 변경해줄 필요가 있다.
    4. 깨지기 쉬운 테스트
      • 애플리케이션의 특정 부분이 다른 부분에 이상한 방법으로 영향을 끼친다는 의미이다. 연결이 끊거나 두 부분을 합하는 것을 통해 멀리 떨어진 것은 영향력이 없어지도록 설계해야 한다.

    TDD로 프레임워크를 만들려면 어떻게 해야 하나?

    오늘을 위해 코딩하고 내일을 위해 설계해라 TDD 는 이것을 뒤집었다. 내일을 위해 코딩하고 오늘을 위해 설계해라

    1. 첫 번째 기능을 구현한다.
      • 이 첫 번째 기능은 단순하고 직관적으로 구현되고, 따라서 짧은 시간안에 결함도 적은 상태로 완성한다.
    2. 첫 번째 기능에 대한 변주가 되는 두 번째 기능을 구현한다.
      • 두 기능 사이의 중복이 한 곳으로 모이고, 서로 다른 부분은 다른 곳(메서드, 클래스)으로 옮겨진다.
    3. 앞의 두 기능에 대한 변주로 세 번째 기능을 구현한다.
      • 공통의 로직은 약간의 수정만을 통해 재활용 가능한 상태로 만들어질 수 있을 것이다. 그리고 공통적이지 않은 로직들은 다른 메서드 혹은 클래스 등 명확하게 로직이 있어야 할 곳에 있게 되는 경향이 있다.

    OCP는 서서히 지켜져 가는데, 실제로 발생하는 변주들에 대해서는 특히 더 그렇다. 테스트 주도 개발은 비록 발생하지 않은 변주 종류는 잘 표현하지 못할지라도 발생하는 변주 종류들은 잘 표현하는 프레임 워크를 만들게 해준다.
    미래에 OCP를 위배하더라도 그것을 바로 잡아줄 테스트가 존재하기에 큰 비용은 발생하지 않는다.

    피드백이 얼마나 필요한가?

    TDD 의 테스트에 대한 관점은 실용적이다.
    TDD 에서 테스트는 어떤 목적을 위한 하나의 수단이다.
    만약 어떤 구현에 대한 지식이 신뢰할 만 하다면 그에 대한 테스트는 작성하지 않을 것이다.
    의도적으로 구현을 무시하는 블랙 박스 테스팅은 몇 가지 이점이 있다. 블랙 박스 테스팅은 코드를 무시함으로써 하나의 다른 가치 체계를 드러낸다.

    테스트를 지워야 할 때는 언제인가?

    1. 테스트 삭제 시 자신감이 줄어들 것 같으면 절대 지우지 말자
    2. 두 개의 테스트 코드가 동일한 부분을 실행하더라도 이 둘이 서로 다른 시나리오를 말한다면 그대로 남겨야 한다.

    프로그래밍 언어나 환경이 TDD에 어떤 영향을 주는가?

    TDD 주기를 수행하기가 힘든 언어나 환경에서 작업 시 단계가 커지는 경향이 있다.

    ###거대한 시스템을 개발할 때에도 TDD를 할 수 있는가?
    시스템에 있는 기능의 양은 TDD의 효율에 영향을 미치지 않는 것 같다. 중복을 제거함에 따라 더 작은 객체들이 만들어지게 되고, 이 작은 객체들은 애플리케이션의 크기와 무관하게 독립적으로 테스트 될 수 있다.

    애플리케이션 수준의 테스트로도 개발을 주도할 수 있는가?

    두 가지 문제가 존재한다.

    1. 기술적 문제
      • 구현되지 않은 기능에 대해 테스트 코드를 작성하는 것
    2. 사회적 문제
      • 테스트 작성이 사용자에게 기존에 없던 새로운 책임이 된다. 여기서 책임은 구현하기 전 이라는 기존에 존재하지 않던 단계에서 수행되어야 한다.

    프로젝트 중반에 TDD를 도입하려면 어떻게 해야 할까?

    테스트를 염두하지 않고 만든 코드는 테스트 하기가 어렵다. 인터페이스 설계가 되어 있지 않기에 일부분 격리 후 실행하는게 어렵다.
    고치면 되잖아 라고 말할 수 있지만 리팩토링 과정에서 발생하는 에러를 도와주는 테스트 코드가 없기에 문제가 발생할 수 있다.
    절때 하면 안되는 작업
    - 전체 코드를 위하여 모든 테스트를 제작하고 코드를 리팩토링 하는 작업
    - 몇달은 걸릴텐데 그 동안 모든 새로운 기능은 STOP 된다.
    우선 해야할 작업
    - 변경 범위 제한
    - 시스템이 단순하게 보일 수 있지만 일단 보류
    - 테스트와 리팩토링 사이에 데드락을 풀어주는 작업

    TDD는 누구를 위한 것인가?

    TDD가 없다면 일년이 이상 지나게 될 경우 코드가 썩어가는게 보이지만 TDD는 시간이 지남에 따라 코드에 대한 자신감을 더 쌓게 해준다.

    어째서 TDD가 잘 작동하는가?

    테스트 코드를 통해 결험을 빨리 발견하여 수정비용이 줄어든다.
    설계 결정에 대한 피드백 고리를 단축시킨다.

    왜 TDD 인지?

    1. 테스트
      • 자동화 되고 구체적인 테스트를 의미
      • TDD는 분석 기술이자 설계 기술이기도 하다. 사실은 개발의 모든 활동을 구조화 하는 기술이다.
    2. 주도
      • 개발을 테스트로 주도 하지 않는다면 무엇으로 주도할 것인가? (추측, 명세??)
    3. 개발
      • 분석, 논리적 설계, 물리적 설계, 구현, 테스팅, 검토, 통합, 배포를 아우르는 복잡한 춤을 의미한다.

    'Spring' 카테고리의 다른 글

    Maven QueryDSL QClass 에러  (0) 2023.01.24
    TDD[2]  (0) 2021.10.08
    TDD[1]  (0) 2021.10.08
    Spring Security[2]  (0) 2021.07.19
    Spring Security[1]  (0) 2021.07.19

    댓글

Designed by Tistory.