TDD - 켄트백(3) 패턴

InSeok·2023년 2월 8일
0

TDD

목록 보기
3/4

테스트 우선

  • 테스트는 테스트 대상이 되는 코드를 작성하기 직전에 작성하는 것이 좋다.
  • 테스트는 프로그램 설계와 작업 범위 조절에 유용하다.

단언 우선

  • 테스트를 작성할 때 단언을 제일 먼저 쓰고 시작하자.
  • 단언을 먼저 작성하면 작업을 단순하게 만드는 효과를 볼 수 있다.
  • 시스템을 개발할 때 완료된 시스템이 어떨거라고 알려주는 이야기 부터 작성한다.
  • 특정기능을 개발할 때 기능이 완료되면 통과할 수 있는 테스트부터 작성한다.
  • 테스트를 개발할 때 완료될 때 통과해야할 단언부터 작성한다.

테스트 데이터

  • 테스트할 때 어떤 데이터를 사용해야 할까?
    • 테스트를 읽을 때 쉽고 따라가기 좋을 만한 데이터를 사용하라.
    • 실제 데이터(실제 실행을 통해 수집한 이벤트 결과)를 사용하는것도 좋은 방법이다.
    • 명백한 데이터를 사용하자.

한 단계 테스트

  • 목록에서 다음 테스트를 고를 때 무엇을 기준으로 할까?
    • 새로운 무언가를 가르쳐 줄 수 있으며, 구현할 수 있다는 확신이 드는 테스트를 고르자.
    • 메서드가 아무 일도 하지 않는 경우를 먼저 테스트하자
    • 아는것에서 모르는것으로 방향을 정하여 테스트를 고르고 작성하자

테스트를 이용하여 묻고, 테스트를 이용하여 설명하자.

  • 외부에서 만든 소프트웨어에 대해 패키지의 새로운 기능을 처음으로 사용해보기 전에 테스트를 작성해 실행해보자

회귀 테스트

  • 회귀테스트란 처음 코딩할 때 작성했어야 하는 테스트다.

  • 시스템 장애가 보고될때, 무슨 일을 제일 먼저 해야할까?

    • 그 장애로 인하여 실패하는 테스트 수정
    • 통과할 경우 장애가 수정 되었다고 볼수 있는 테스트작성
  • 회귀 테스트를 작성할 때는 이 테스트를 작성해야한다는 사실을 어떻게 하면 애초에 알 수 있었을지 생각해보자.

  • 시스템 장애를 손쉽게 격리시킬 수 없다면 리팩토링 해야한다.

  • 키보드로 뭘 쳐야 할지 알면, 명백한 구현을 하고, 잘 모르겠다면 가짜 구현을 하자.

자식 테스트

  • 지나치게 큰 테스트 케이스를 돌아가게 만들려면, 원래 테스트 케이스의 깨지는 부분에 해당하는 작은 테스트 케이스를 작성하고 그 작은 테스트 케이스가 실행되도록 한다. 그 후, 다시 원래의 큰 테스트 케이스를 추가하자.
  • 빨강/초록/리팩토링 리듬은 성공이 지속되는 데 가장 중요하다. 최대한 유지하도록 노력하자
  • 큰 테스트를 작성했을 경우, 어떻게 하면 좀더 작게 만들 수 있었을지 고민해보자.

모의 객체

  • 비용이 많이 들거나 복잡한 리소스에 의존하는 객체를 테스트 하려면 어떻게 해야 할까?
    • 상수를 반환하게끔 만든 속임수 버전의 리소스(모의 객체)를 만들면된다.
    • 모의 객체를 사용하려면, 전역변수에 저장해서는 안되며, 사용할경우 테스트 실행 후 다시 전역변수를 복구시켜 놓아야한다.

셀프 션트

  • 한 객체가 다른 객체와 올바르게 대화하는지 테스트하려면 어떻게 해야 할까?
    • 테스트 대상이 되는 객체가 원래의 대화 상대가 아니라 테스트 케이스와 대화하도록 만들면 된다.
    • 테스트 케이스가 구현할 인터페이스를 얻기 위해 인터페이스 추출을 해햐한다.

로그 문자열

  • 메시지의 호출 순서가 올바른지 검사하려면 어떻게 해야 할까?
    • 로그 문자열을 가지고 있다가 메시지가 호출될 때마다 그 문자열에 추가하자.

크래시 테스트 더미

  • 호출되지 않을 것 같은 에러코드를 어떻게 테스트 할까?
    • 실제 작업을 수행하는 대신 그냥 예외를 발생시키기만 하는 특수한 객체를 만들어서 이를 호출한다.
  • 수많은 에러 상황에 대해서는 어떻게 테스트 해야할까?
    • 작동하길 원하는 부분에 대해서만 테스트 하면 된다.
    • 자바 익명 내부클래스를 통해 우리가 테스트하기 원하는 적적한 메서드만이 오류를 발생시키도록 할 수 있다.

깨끗한 체크인

  • 팀 프로그래밍을 할때, 코드를 체크인(커밋)하기 전에 항상 모든 테스트가 돌아가는 상태로 만들어 두어야 한다.
  • 통합 테스트 슈트에서 테스트가 실패하는 경우엔 어떻게 해야 할까?
    • 그동안 작업한 코드를 날려버리고 다시 하거나, 수정후 다시 실행해보자.
    • 실패한 테스트는 방금 만들어낸 프로그램을 완전히 이해하지 못했음을 말해주는 증거다.

코드가 테스트를 통과하게 만들기 위해 사용가능한 패턴들

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

  • 실패하는 테스트를 만든 후 첫 번째 구현은 어떻게 할까?
    • 상수를 반환하게 하자.
    • 일단 테스트가 통과하면 단계적으로 상수를 변수를 사용하는 수식으로 변형한다.
  • 가짜 구현하기를 해야 하는이유
    • 막대가 초록색일때, 자신이 어디에 서 있는지 알 수 있고, 확신을 갖고 거기부터 리팩토링해 갈 수 있다.
    • 하나의 구체적인 예에서 시작해서 일반화하게 되면, 쓸데 없는 고민을 줄일 수 있다.

테스트 케이스 => 말 그대로 Case. 해당 테스트 유닛에 들어가는 Input. 또는 해당 테스트의 수행 n번. (일반적으로 함수 호출)

테스트 유닛 => 테스트를 하는 단위 기능. (일반적으로 함수)

테스트 슈트 => 테스트 유닛의 집합. (일반적으로 함수 묶음)

  • 단순한 연산들은 그냥 구현해버리자
  • 제대로 동작하는을 푼후에 깨끗한 코드를 느긋하게 해결하자
  • 객체 컬렉션을 다루는 연산은 어떻게 구현할까?
    • 일단 컬렉션 없이 구현하고 그 다음에 컬렉션을 사용하게 하자.

xUnit 패턴

단언(assertion)

  • 테스트가 잘 작동하는지 어떻게 검사할 것인가?
    • boolean 수식을 작성해서 프로그램이 자동으로 코드가 동작하는지에 대한 판단을 수행하도록하자.
  • 테스트를 완전히 자동화하려면 결과를 평가하는데 개입되는 인간의 판단을 모조리 끄집어 내야한다.

픽스처

  • 여러 테스트에서 공통으로 사용하는 객체들을 생성할때 어떻게 하면 좋을까?
  • 각 테스트 코드에 있는 지역 변수를 인스턴스 변수로 바꾸고 setUP() 매서드를 재정의하여 이 메서드에서 인스턴스 변수들을 초기화 하도록 한다.

테스트 픽스처: 여러 테스트에 걸쳐 동일한 객체들을 세팅하는 코드

  • 새로운 종류의 픽스처는 각각 TestCase의 새로운 하위 클래스여야 한다.
  • 이 하위 클래스의 인스턴스 안에서 새로운 픽스처가 각각 생성되어 한번 사용된후 버려진다.
  • 한 픽스처가 여러클래스를 테스트하는데 쓰이는 경도 있고, 한 모델 클래스를 테스트하는데 두 세개의 픽스처가 필요한 경우도 있다.

외부픽스처

  • 픽스처 중 외부 자원이 있을경우, tearDown()메서드를 재정의하여 자원을 해제하면된다.

테스트 메서드

  • 테스트를 작성하기 전에 짧은 아웃라인을 적어보자
    • ex) @Display("터플 공간에서 읽어들이기")

예외 테스트

  • 예외가 발생하는 것이 정상인 경우에 대한 테스트는 예상되는 예외를 잡아서 무시하고, 예외가 발생하지 않는 경우에 한해서 테스트가 실패하게 만들면 된다.

→ 요새는 assertThrows를 통해 예상하는 예외가 발생하는지 체크가능하다.

디자인 패턴

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

커맨드

  • 간단한 메서드 호출이 아닌 복잡한 형태의 계산 작업에 대한 호출이 필요하다면 어떻게 해야 할까?
    • 계산작업에 대한 객체를 생성하여 이를 호출하면 된다.
    • 객체를 생성할 때 계산에 필요한 모든 매개 변수들을 초기화하고, 호출할 준비가 되면 run()과 같은 프로토콜을 이용해서 계산을 호출한다.

값객체

  • 널리 공유해야 하지만 동일성은 중요하지 않을때 객체를 어떤식으로 설계할 수 있을까?
    • 객체가 생성될 때 객체의 상태를 설정한 후 이 상태가 절대 변할 수 없도록 한다.
    • 이 객체에 대해 수행되는 연산은 언제나 새로운 객체를 반환하게 만든다.
    • 값객체를 구현할 때는 모든 메서드는 기존객체는 변하지 않은 채로 놔두고, 새로운 객체를 반환해야한다.
    • 모든 값객체는 동등성을 구현해야한다.
  • 객체의 별칭문제를 해결하는 방법
    1. 현재 의존하는 객체에 대한 참조를 결코 외부로 알리지 않는 방법

      그 대신, 객체에 대한 복사본을 제공

    2. 옵저버 패턴

      1. 의존하는 객체에 자기를 등록해 놓고, 객체의 상태가 변하면 통지를 받는 방법
      2. 제어 흐름을 이해하기 어렵게 만들 수 있고, 의존성을 설정하고 제거하기 위한 로직이 지저분해질 수 있다
    3. 객체를 변화가 불가능한 상태로 만든다.

템플릿 메서드

  • 작업 순서는 변하지 않지만 각 작업단위에 대한 미래의 개선 가능성을 열어두고싶은 경우 어떻게 표현할까?
    • 다른 메서드들을 호출하는 내용으로만 이루어진 메서드를 만든다.
    • 상위 클래스에는 다른 메서드를 호출하는 내용으로만 이루어진 메서드를 만들고, 하위클래스에서는 이 각각의 메서드를 서로 다른 방식으로 구현한다.
    • 두 하위 클래스에서 어떤 연산 순서의 두가지 변주를 발견하면, 양자가 점차 가까워지도록 둘을 같이 움직여가고, 나머지 메서드들과는 다른 부분을 추출해 내면 남는 것은 템플릿 메서드가 된다. 템플릿메서드를 상위클래스로 보내고 중복을 제거할 수 있다.

플러거블 셀렉터

  • 인스턴스 별로 서로 다른 메서드가 동적으로 호출되게 하려면 어떻게 해야 할까?
    • 메서드의 이름을 저장하고 있다가 그 이름에 해당하는 메서드를 동적으로 호출한다.
  • 각각 단지 메서드 하나만 구현하는 하위클래스가 여러개일경우, switch문을 갖는 하나의 클래스를 만들어 필드의 값에 따라 서로 다른 메서드를 호출하게 하면된다.
    • 하지만, 새로운 종류의 출력을 추가할때마다 출력메서드를 추가하고 switch문을 바꿔야한다는 단점이 있다.
    • 이때 리플랙션으 이용하여 동적으로 메서드를 호출하면, swtich문을 없앨수 있다.

팩토리 메서드(객체를 생성하는 메서드)

  • 새 객체를 만들때 유연성을 원하는 경우 객체를 어떻게 생성해야 할까?
    • 생성자를 쓰는대신 일반 메서드에서 객체를 생성한다.

사칭 사기꾼

  • 기존의 코드에 새로운 변이를 도입하려면, 기존의 객체와 같은 인터페이스를 갖지만 구현은 다른 새로운 객체를 추가하면된다.
  • 리팩토링 중 나타나는 사칭 사기꾼 예
    • 널객체 - 데이터가 없는 상태를 데이터가 있는 상태와 동일하게 취급가능
    • 컴포지트 - 객체의 집합을 단일 객체처럼 취급 가능

컴포지트

  • 하나의 객체가 다른 객체 목록의 행위를 조합한 것 처럼 행동하게 만드려면 어떻게 해야 할까?
    • 객체 집합을 나타내는 객체를 단일 객체에 대한 사칭 사기꾼로 구현한다.

수집 매개변수

  • 여러 객체에 걸쳐 존재하는 메서드의 결과를 수집하려면 어떻게 해야 할까?
    • 결과가 수집될 객체를 각 오퍼레이션의 매개 변수로 추가한다.
profile
백엔드 개발자

0개의 댓글